├── COPYING ├── README.md ├── data └── com.github.manhelper.desktop.in ├── manhelper_screenshot.png ├── meson.build ├── postinstall.sh ├── src ├── Application.vala ├── Bookmarks.vala ├── KeywordList.vala ├── MainWin.vala ├── MultiTabPager.vala ├── PageZoomer.vala ├── PreferDialog.vala ├── SearchDialog.vala ├── SectionList.vala └── ThemeCSS.vala └── ui ├── about_dialog.ui ├── bookmarks_dialog.ui ├── icon_manhelper.png ├── keyword_list.ui ├── manhelper.gresource.xml ├── manhelper.ui ├── multitab_pager.ui ├── page_zoomer.ui ├── prefer_dialog.ui ├── search_dialog.ui ├── section_list.ui └── theme_dialog.ui /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Man Helper 2 | Man Helper is a lightweight GUI front-end for man2html under development using Vala and GTK. It features an easy-to-use interface, and aims for a modern GUI viewer for man pages. 3 | ![Man Helper Screenshot](./manhelper_screenshot.png "Man Helper running on Linux Mint") 4 | 5 | ## Building and Installation 6 | You'll need the following dependencies to build: 7 | 8 | * gobject-introspection 9 | * libgda-5.0-dev 10 | * libgtk-3-dev (>= 3.24.0) 11 | * libjson-glib-dev 12 | * libsoup2.4-dev 13 | * libwebkit2gtk-4.0-dev 14 | * libxml2-dev 15 | * meson 16 | * valac 17 | 18 | You'll need the following dependencies to run: 19 | 20 | * apache2 21 | * man2html 22 | 23 | Run `meson build` to configure the build environment. Change to the build directory and run `ninja` to build 24 | 25 | meson build --prefix=/usr 26 | cd build 27 | ninja 28 | 29 | To install, use `ninja install`, then execute with `com.github.manhelper` 30 | 31 | ninja install 32 | com.github.manhelper 33 | 34 | ## Features 35 | 36 | - [x] GUI front-end 37 | - [x] find in man page 38 | - [x] search in man database 39 | - [x] bookmarks 40 | - [x] multi-tab views 41 | - [x] zoom in/out 42 | - [x] preferences (font family, theme color, etc) 43 | - [x] sections 44 | - [ ] translation 45 | -------------------------------------------------------------------------------- /data/com.github.manhelper.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=Man Helper 4 | GenericName=Help Viewer 5 | Comment=View Man Pages 6 | Exec=com.github.manhelper 7 | Icon=com.github.manhelper 8 | Terminal=false 9 | Type=Application 10 | Categories=Development;Utility;System 11 | -------------------------------------------------------------------------------- /manhelper_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akarin123/manhelper/cdce635416fdd324c1644c1c00ac80db261b3ff1/manhelper_screenshot.png -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('com.github.manhelper', 'vala', 'c', version: '0.2.0') 2 | 3 | cc = meson.get_compiler('c') 4 | m_dep = cc.find_library('m', required : false) 5 | gnome = import('gnome') 6 | 7 | gresources = gnome.compile_resources( 8 | meson.project_name()+'_resources', 9 | 'ui/manhelper.gresource.xml', 10 | c_name: 'resources', 11 | source_dir:['ui'] 12 | ) 13 | 14 | dependencies = [ 15 | dependency('glib-2.0'), 16 | dependency('gobject-2.0'), 17 | dependency('gtk+-3.0',version:'>=3.24.0'), 18 | dependency('gmodule-2.0'), 19 | dependency('webkit2gtk-4.0'), 20 | dependency('libsoup-2.4'), 21 | dependency('libgda-5.0'), 22 | dependency('libxml-2.0'), 23 | dependency('json-glib-1.0'), 24 | m_dep, 25 | ] 26 | 27 | sources = files('src/Application.vala', 28 | 'src/MainWin.vala', 29 | 'src/SearchDialog.vala', 30 | 'src/KeywordList.vala', 31 | 'src/Bookmarks.vala', 32 | 'src/MultiTabPager.vala', 33 | 'src/PageZoomer.vala', 34 | 'src/PreferDialog.vala', 35 | 'src/SectionList.vala', 36 | 'src/ThemeCSS.vala',) 37 | 38 | app = executable(meson.project_name(), 39 | sources, 40 | gresources, 41 | dependencies: dependencies, 42 | install: true,) 43 | 44 | meson.add_install_script('./postinstall.sh') 45 | -------------------------------------------------------------------------------- /postinstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | 3 | # enable cgid module of apache 4 | sudo a2enmod cgid 5 | sudo service apache2 restart 6 | 7 | -------------------------------------------------------------------------------- /src/Application.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 XX Wu 3 | * 4 | * This file is part of Man Helper 5 | * 6 | * Man Helper is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * Man Helper is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * You should have received a copy of the GNU General Public License 15 | * along with Man Helper. If not, see . 16 | * 17 | * Authored by: XX Wu 18 | */ 19 | 20 | namespace ManHelper 21 | { 22 | /* Main application. */ 23 | public class App: Gtk.Application 24 | { 25 | public uint section_num_max {get;default=9;} 26 | public MainWin win; 27 | 28 | internal string startup_filename {set;get;default="startup.js";} 29 | 30 | internal string bookmarks_parent_dir {set;get;default=".";} 31 | internal string bookmarks_directory {set;get;default="/.manhelper";} 32 | internal string bookmarks_filename {set;get;default="bookmarks";} 33 | internal DataBase bookmarks_db = null; 34 | private Gdk.Pixbuf app_icon; 35 | 36 | protected override void startup() 37 | { 38 | base.startup(); 39 | DataBase.init_database_directory(this); 40 | } 41 | 42 | protected override void activate() 43 | { 44 | var app_win = new MainWin(this); 45 | 46 | app_win.show_all(); 47 | 48 | this.win = app_win; 49 | 50 | try 51 | { 52 | app_icon = new Gdk.Pixbuf.from_resource("/ui/icon_manhelper.png"); 53 | app_icon = app_icon.scale_simple(128,128,Gdk.InterpType.TILES); 54 | app_win.icon = app_icon; 55 | } 56 | catch (Error e) 57 | { 58 | message(e.message); 59 | } 60 | } 61 | 62 | } 63 | 64 | /* This is the About dialog. */ 65 | [GtkTemplate (ui = "/ui/about_dialog.ui")] 66 | public class AboutDialog: Gtk.AboutDialog 67 | { 68 | internal AboutDialog () 69 | { 70 | Gdk.Pixbuf dialog_pixbuf; 71 | 72 | try 73 | { 74 | dialog_pixbuf = new Gdk.Pixbuf.from_resource("/ui/icon_manhelper.png"); 75 | dialog_pixbuf = dialog_pixbuf.scale_simple(128,128,Gdk.InterpType.TILES); 76 | this.logo=dialog_pixbuf; 77 | } 78 | catch (Error e) 79 | { 80 | //print("Unable to load the logo\n"); 81 | message(e.message+"\n"); 82 | } 83 | } 84 | } 85 | 86 | } 87 | 88 | public static int main (string[] args) 89 | { 90 | var app = new ManHelper.App(); 91 | 92 | return app.run(args); 93 | } -------------------------------------------------------------------------------- /src/Bookmarks.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 XX Wu 3 | * 4 | * This file is part of Man Helper 5 | * 6 | * Man Helper is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * Man Helper is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * You should have received a copy of the GNU General Public License 15 | * along with Man Helper. If not, see . 16 | * 17 | * Authored by: XX Wu 18 | */ 19 | 20 | namespace ManHelper 21 | { 22 | /* This manages bookmarks of the program. */ 23 | [GtkTemplate (ui = "/ui/bookmarks_dialog.ui")] 24 | private class BookmarksDialog: Gtk.Dialog 25 | { 26 | [GtkChild] 27 | private unowned Gtk.TreeView bookmarks_view; 28 | 29 | private MainWin win; 30 | private Gtk.ListStore list_store; 31 | private Gtk.TreeIter list_iter; 32 | 33 | public BookmarksDialog (MainWin win) 34 | { 35 | this.win = win; 36 | list_store = new Gtk.ListStore(1,Type.STRING); 37 | 38 | bookmarks_view.set_model(list_store); 39 | var column = new Gtk.TreeViewColumn(); 40 | 41 | var title = new Gtk.CellRendererText(); 42 | //var intro = new Gtk.CellRendererText(); 43 | 44 | column.pack_start(title, true); 45 | column.set_attributes(title, "text", 0); 46 | 47 | bookmarks_view.append_column(column); 48 | 49 | var query = new SelectQuery(); 50 | var bookmarks_db = win.app.bookmarks_db; 51 | query.connection = bookmarks_db.connection; 52 | //win.bookmarks_db.show_data(query); 53 | try 54 | { 55 | var contents = query.get_table_contents(); 56 | 57 | var len_bookmarks = contents.get_n_rows(); 58 | 59 | //print(@"$(len_bookmarks) bookmarks\n"); 60 | for (var ii=0;ii title_list = new SList(); 99 | 100 | title_list.append(title_v); 101 | 102 | //title_list.append("man"); 103 | var contents = query.get_table_contents(); 104 | var row_num = contents.get_row_from_values(title_list,{0}); 105 | 106 | var uri = contents.get_value_at(1,row_num).get_string(); 107 | this.win.view_current.load_uri(uri); 108 | 109 | title_v.unset(); 110 | } 111 | catch (Error e) 112 | { 113 | message(e.message); 114 | } 115 | } 116 | } 117 | 118 | [GtkCallback] 119 | private void on_btn_delete_clicked (Gtk.Button self) 120 | { 121 | var query = new SelectQuery(); 122 | var bookmarks_db = this.win.app.bookmarks_db; 123 | 124 | query.connection = bookmarks_db.connection; 125 | 126 | Gtk.TreeModel tree_model; 127 | Gtk.TreeIter tree_iter; 128 | bool selected; 129 | //win.bookmarks_db.show_data(query); 130 | var selection = this.bookmarks_view.get_selection(); 131 | selected = selection.get_selected(out tree_model, out tree_iter); 132 | 133 | if (selected) 134 | { 135 | try 136 | { 137 | Value title_v; 138 | tree_model.get_value(tree_iter,0,out title_v); 139 | 140 | string title = title_v.get_string(); 141 | 142 | bookmarks_db.run_query(@"DELETE FROM bookmarks WHERE title = \"$(title)\""); 143 | ((Gtk.ListStore)tree_model).remove(ref tree_iter); 144 | 145 | title_v.unset(); 146 | } 147 | catch (Error e) 148 | { 149 | message(e.message); 150 | } 151 | } 152 | } 153 | 154 | [GtkCallback] 155 | private void on_btn_close_clicked (Gtk.Button self) 156 | { 157 | this.destroy(); 158 | } 159 | } 160 | 161 | private class SelectQuery:Object 162 | { 163 | /* "*" in SQL matches any number of any characters */ 164 | public string field { set; get; default = "*"; } 165 | public string table { set; get; default = "bookmarks"; } 166 | public Gda.Connection connection { set; get; } 167 | 168 | public Gda.DataModel get_table_contents () throws Error requires (this.connection.is_opened()) 169 | { 170 | //print("Building query...\n"); 171 | /* Build select query */ 172 | var builder = new Gda.SqlBuilder(Gda.SqlStatementType.SELECT); 173 | builder.select_add_field (this.field, null, null); 174 | builder.select_add_target(this.table, null); 175 | 176 | var statement = builder.get_statement(); 177 | //print("Executing...\n"); 178 | return this.connection.statement_execute_select(statement, null); 179 | } 180 | } 181 | 182 | internal class DataBase:Object 183 | { 184 | /* Using defaults will search a SQLite database located at current directory called bookmarks.db*/ 185 | public string provider { set; get; default = "SQLite"; } 186 | public string db_file { set; get; default = "SQLite://DB_DIR=.;DB_NAME=bookmarks"; } 187 | public Gda.Connection connection; 188 | 189 | public DataBase(string path,string filename) 190 | { 191 | this.db_file = "SQLite://DB_DIR="+path+";DB_NAME="+filename; 192 | 193 | try 194 | { 195 | this.open(); 196 | this.create_tables(); 197 | //var q = new SelectQuery(); 198 | //q.connection = this.connection; 199 | //this.show_data(q); 200 | //bookmarks_db.create_tables(); 201 | } 202 | catch (Error e) 203 | { 204 | message(e.message); 205 | } 206 | } 207 | 208 | public static void init_database_directory (App app) 209 | { 210 | var home_dir = Environment.get_home_dir(); 211 | 212 | if (home_dir!=null) 213 | { 214 | //print(home_dir+"\n"); 215 | var bookmarks_dirpath = home_dir+app.bookmarks_directory; 216 | 217 | if (FileUtils.test(bookmarks_dirpath, FileTest.IS_DIR)) 218 | { 219 | app.bookmarks_parent_dir = home_dir; 220 | } 221 | else 222 | { 223 | var file_temp = File.new_for_path(bookmarks_dirpath); 224 | try 225 | { 226 | if (file_temp.make_directory(null)) 227 | { 228 | app.bookmarks_parent_dir = home_dir; 229 | } 230 | } 231 | catch (Error e) 232 | { 233 | message(e.message); 234 | } 235 | } 236 | } 237 | } 238 | 239 | public void open() throws Error 240 | { 241 | //print("Opening Database connection...\n"); 242 | this.connection = Gda.Connection.open_from_string (null, this.db_file, null, Gda.ConnectionOptions.NONE); 243 | } 244 | 245 | /* Create a bookmark table */ 246 | public void create_tables() throws Error requires (this.connection.is_opened()) 247 | { 248 | //print("Creating table...\n"); 249 | this.run_query("CREATE TABLE IF NOT EXISTS bookmarks (title string PRIMARY KEY,uri string)"); 250 | 251 | } 252 | 253 | public int run_query (string query) throws Error requires (this.connection.is_opened()) 254 | { 255 | //print(@"Executing query: [$(query)]\n"); 256 | return this.connection.execute_non_select_command (query); 257 | } 258 | 259 | /* 260 | public void show_data (SelectQuery query) throws Error requires (this.connection.is_opened()) 261 | { 262 | try 263 | { 264 | var contents = query.get_table_contents(); 265 | 266 | //print("Table: '%s'\n%s", query.table, contents.dump_as_string()); 267 | //print("Table: \n%s", contents.dump_as_string()); 268 | } 269 | catch (GLib.Error e) 270 | { 271 | message(e.message); 272 | } 273 | }*/ 274 | } 275 | } -------------------------------------------------------------------------------- /src/KeywordList.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 XX Wu 3 | * 4 | * This file is part of Man Helper 5 | * 6 | * Man Helper is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * Man Helper is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * You should have received a copy of the GNU General Public License 15 | * along with Man Helper. If not, see . 16 | * 17 | * Authored by: XX Wu 18 | */ 19 | 20 | namespace ManHelper 21 | { 22 | /* Search list of man pages from entry text*/ 23 | [GtkTemplate (ui = "/ui/keyword_list.ui")] 24 | private class KeywordList: Gtk.Window 25 | { 26 | public string keyword {get;set;default="";} 27 | public int find_num {get;set;default=0;} 28 | private MainWin win; 29 | //private int x_list; 30 | //private int y_list; 31 | private Gtk.MenuItem last_selected = null; 32 | 33 | [GtkChild] 34 | private unowned Gtk.ScrolledWindow keywordscrolled; 35 | 36 | [GtkChild] 37 | internal unowned Gtk.MenuBar keywordmenu; 38 | //private Gtk.Box keywordbox; 39 | private string man_stdout; 40 | private string man_stderr; 41 | private int man_status; 42 | private string[] man_entries; 43 | private int max_show = 15; 44 | private int min_show = 5; 45 | //private Gtk.MenuItem menu_item; 46 | private int menu_item_height = -1; 47 | 48 | 49 | public KeywordList (MainWin win, string keyword) 50 | { 51 | this.win = win; 52 | this.keyword = keyword.replace("\\",""); /* Avoid syntax error for man -k */ 53 | 54 | /* Find section number, 0 is "All Sections" */ 55 | var section_num = this.win.section_list.section_combo.get_active(); 56 | 57 | try 58 | { 59 | if (section_num >= 1) 60 | { 61 | Process.spawn_command_line_sync (@"man -k -s $(section_num) "+"\""+this.keyword+"\"",out man_stdout,out man_stderr,out man_status); 62 | } 63 | else 64 | { 65 | Process.spawn_command_line_sync ("man -k "+"\""+this.keyword+"\"",out man_stdout,out man_stderr,out man_status); 66 | } 67 | } 68 | catch (SpawnError e) 69 | { 70 | message(e.message); 71 | } 72 | man_entries = man_stdout.split("\n"); 73 | 74 | if (man_entries.length>0) 75 | { 76 | find_num = man_entries.length-1; /* string.split() will add an extra "\n" string at the end */ 77 | man_entries=man_entries[0:find_num]; 78 | } 79 | 80 | for (var index=0;index{update_keyword_list_geom(this.win);return Source.CONTINUE;}); 113 | 114 | this.set_transient_for(win); 115 | } 116 | 117 | internal void update_keyword_list_pos (MainWin win) 118 | { 119 | Gtk.Allocation entry_allcation; 120 | Gdk.Window search_list_gdkwin; 121 | 122 | if (this.get_realized()) 123 | { 124 | win.entry_search.get_allocation(out entry_allcation); 125 | search_list_gdkwin = this.get_window(); 126 | search_list_gdkwin.move_to_rect(entry_allcation,Gdk.Gravity.SOUTH_WEST,Gdk.Gravity.NORTH_WEST,Gdk.AnchorHints.RESIZE_Y,0,0); 127 | } 128 | 129 | } 130 | 131 | internal void update_keyword_list_geom(MainWin win) 132 | { 133 | int width_new; 134 | Gtk.MenuItem selected_item = null; 135 | 136 | if (this.get_realized()) 137 | { 138 | update_keyword_list_pos(win); 139 | } 140 | 141 | width_new = win.entry_search.get_allocated_width(); 142 | 143 | if ((this.visible)&&(width_new-this.width_request).abs()>1) 144 | { 145 | this.width_request=width_new; 146 | this.resize(this.width_request,this.height_request); 147 | //print(@"width now:$(width_new),$(this.width_request), time:"+Time.local(time_t()).to_string()+"\n"); 148 | } 149 | 150 | 151 | if (this.get_realized()) 152 | { 153 | selected_item = this.keywordmenu.get_selected_item() as Gtk.MenuItem; 154 | this.last_selected = selected_item; 155 | //print(selected_item.label+"\n"); 156 | } 157 | 158 | //return true; 159 | } 160 | 161 | [GtkCallback] 162 | private bool key_up_and_down (Gtk.Widget self,Gdk.Event evnt) 163 | { 164 | Gdk.EventKey key_evnt; 165 | uint keyval; 166 | 167 | key_evnt = evnt.key; 168 | keyval = key_evnt.keyval; 169 | 170 | if ((keyval == Gdk.Key.Up)||(keyval == Gdk.Key.Down)) 171 | { 172 | int item_x,item_y; 173 | Gtk.Adjustment vadj; 174 | Gtk.MenuItem selected_item; 175 | int vadj_step = menu_item_height*max_show/3; 176 | if (this.get_realized()) 177 | { 178 | selected_item = this.keywordmenu.get_selected_item() as Gtk.MenuItem; 179 | 180 | if (selected_item!=null) 181 | { 182 | selected_item.translate_coordinates(this,0,0,out item_x,out item_y); 183 | 184 | if (item_y>this.height_request) 185 | { 186 | vadj = this.keywordscrolled.get_vadjustment(); 187 | while (item_y>this.height_request) 188 | { 189 | vadj.value = vadj.value+vadj_step; 190 | selected_item.translate_coordinates(this,0,0,out item_x,out item_y); 191 | } 192 | //vadj.value +=item_y; 193 | this.keywordmenu.select_item(selected_item); 194 | //this.show(); 195 | } 196 | else if(item_y<0) 197 | { 198 | vadj = this.keywordscrolled.get_vadjustment(); 199 | while (item_y<0) 200 | { 201 | vadj.value = vadj.value-vadj_step; 202 | selected_item.translate_coordinates(this,0,0,out item_x,out item_y); 203 | } 204 | this.keywordmenu.select_item(selected_item); 205 | //this.show(); 206 | } 207 | } 208 | } 209 | 210 | /* need to turn off the mouse event, 211 | but I haven't found a proper way */ 212 | // future work here 213 | } 214 | 215 | return false; 216 | } 217 | [GtkCallback] 218 | private bool escape_key_destroy (Gtk.Widget self,Gdk.Event evnt) 219 | { 220 | Gdk.EventKey key_evnt; 221 | uint keyval; 222 | 223 | key_evnt = evnt.key; 224 | keyval = key_evnt.keyval; 225 | 226 | if (keyval == Gdk.Key.Escape) 227 | { 228 | this.destroy(); 229 | } 230 | 231 | return false; 232 | } 233 | 234 | [GtkCallback] 235 | private bool mouse_leave_keyword_list (Gtk.Widget self,Gdk.Event evnt) 236 | { 237 | Gdk.EventCrossing evnt_cross; 238 | 239 | evnt_cross = evnt.crossing; 240 | var x = evnt_cross.x; 241 | 242 | if ((last_selected!=null)&&((x<=0)||(x>=this.get_allocated_width()))) 243 | { 244 | this.keywordmenu.select_item(last_selected); 245 | } 246 | 247 | return false; 248 | } 249 | 250 | private void navigate_to_uri (Gtk.MenuItem self) 251 | { 252 | //Gtk.MenuItem selected_item; 253 | string man_entry; 254 | string[] man_data; 255 | string sec_index=""; 256 | string item_uri=""; 257 | //selected_item = this.keywordmenu.get_selected_item() as Gtk.MenuItem; 258 | 259 | man_entry = self.get_label(); 260 | man_data= man_entry.split(" "); 261 | 262 | sec_index=man_data[1].replace("(","").replace(")",""); 263 | //print(man_data[0]+"."+sec_index+"\n"); 264 | item_uri="http://localhost/cgi-bin/man/man2html?"+sec_index+"+"+man_data[0].strip(); 265 | this.win.view_current.load_uri(item_uri); 266 | 267 | /* Setup the CSS theme */ 268 | if ((this.win.prefer_dialog == null) || (!this.win.prefer_dialog.get_realized())) 269 | { 270 | this.win.prefer_dialog = new PreferDialog(this.win); 271 | } 272 | 273 | this.win.prefer_dialog.view = this.win.view_current; 274 | this.win.prefer_dialog.hide(); 275 | /* Add a 50 ms delay */ 276 | Timeout.add(50,()=>{this.win.prefer_dialog.update_page_prefer();return Source.REMOVE;}); 277 | 278 | this.win.last_entry_text = this.keyword; /* Update last entry text */ 279 | 280 | this.destroy(); 281 | } 282 | /* 283 | [GtkCallback] 284 | private bool search_list_follow(Gtk.Widget self, Gdk.EventConfigure evnt) 285 | { 286 | print("get reconfigure signal!"+Time.local(time_t()).to_string()+"\n"); 287 | 288 | return true; 289 | }*/ 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/MainWin.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 XX Wu 3 | * 4 | * This file is part of Man Helper 5 | * 6 | * Man Helper is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * Man Helper is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * You should have received a copy of the GNU General Public License 15 | * along with Man Helper. If not, see . 16 | * 17 | * Authored by: XX Wu 18 | */ 19 | 20 | namespace ManHelper 21 | { 22 | /* This is the main window for the application. */ 23 | [GtkTemplate (ui = "/ui/manhelper.ui")] 24 | public class MainWin: Gtk.ApplicationWindow 25 | { 26 | public App app; 27 | public const string main_title = "Man Helper"; 28 | private string home_uri; 29 | 30 | internal string last_entry_text {set;get;default="";} 31 | internal KeywordList search_list = null; 32 | internal Gtk.FileChooserDialog file_chooser = null; 33 | internal BookmarksDialog bookmarks_dialog = null; 34 | internal SearchDialog search_dialog = null; 35 | internal SectionList section_list = null; 36 | internal MultitabPager pager = null; 37 | internal WebKit.WebView view_current = null; 38 | internal PageZoomer page_zoomer = null; 39 | internal PreferDialog prefer_dialog = null; 40 | //internal Pango.FontDescription prefer_font_desc = null; 41 | internal ThemeCSS prefer_theme_CSS = null; 42 | //internal Gdk.RGBA prefer_backcolor = {}; 43 | 44 | [GtkChild] 45 | private unowned Gtk.Button btn_man; 46 | [GtkChild] 47 | internal unowned Gtk.SearchEntry entry_search; 48 | //[GtkChild] 49 | //internal Gtk.ScrolledWindow scrolled; 50 | [GtkChild] 51 | private unowned Gtk.Box box_mainwin; 52 | 53 | [GtkChild] 54 | private unowned Gtk.Box box_section_list; 55 | [GtkChild] 56 | private unowned Gtk.CheckButton btn_enable_search; 57 | 58 | //internal int search_chars_length = 0; 59 | internal Preferences default_prefer = null; 60 | internal Preferences prefer = null; 61 | 62 | internal MainWin (App app) 63 | { 64 | Object(application: app,title: main_title); 65 | //section_num_max = app.section_num_max; 66 | this.app = app; 67 | this.pager = new MultitabPager(this); /* The Webkit view is packed here */ 68 | this.view_current.button_press_event.connect(on_search_list_outside_mouse_press); 69 | this.box_mainwin.pack_start(pager,true,true,0); 70 | //pager.first_scrolled.add_with_properties(view); 71 | 72 | this.home_uri = "http://localhost/cgi-bin/man/man2html"; 73 | this.view_current.load_uri(home_uri); 74 | //var settings = this.view_current.get_settings(); 75 | //print(settings.enable_javascript.to_string()+"\n"); 76 | //this.start_font_size = settings.get_default_font_size(); 77 | 78 | /* Pack the page_zoomer after Webkit view */ 79 | this.page_zoomer = new PageZoomer(this); 80 | box_mainwin.pack_start(this.page_zoomer,false,false,0); 81 | 82 | /* Pack section list */ 83 | this.section_list = new SectionList(this); 84 | box_section_list.pack_start(this.section_list,true,false,0); 85 | 86 | var bookmarks_dirpath = app.bookmarks_parent_dir+app.bookmarks_directory; 87 | this.app.bookmarks_db = new DataBase(bookmarks_dirpath,app.bookmarks_filename); 88 | 89 | var prefer_dialog = new PreferDialog(this); /* init font size and family */ 90 | prefer_dialog.load_startup_options(app); 91 | this.default_prefer = new Preferences(this); 92 | this.prefer = new Preferences(this); 93 | 94 | //this.prefer_font_desc = prefer_dialog.btn_font.get_font_desc(); 95 | //this.prefer_backcolor = prefer_dialog.btn_backcolor.get_rgba(); 96 | prefer_dialog.hide(); 97 | //this.theme_CSS = new ThemeCSS(); 98 | } 99 | 100 | [GtkCallback] 101 | internal bool on_search_list_outside_mouse_press (Gtk.Widget self,Gdk.EventButton evnt) 102 | { 103 | if ((this.search_list!=null)&&(this.search_list.get_realized())) 104 | { 105 | this.search_list.destroy(); 106 | } 107 | 108 | return false; 109 | } 110 | 111 | [GtkCallback] 112 | private void on_btn_man_clicked (Gtk.Button self) 113 | { 114 | string entry_text = entry_search.get_text(); 115 | Thread[] thread = new Thread[this.app.section_num_max]; 116 | bool[] status = new bool[this.app.section_num_max]; 117 | man_uri[] man_uri_test = new man_uri[this.app.section_num_max]; 118 | bool entry_found = false; 119 | string[] entry_data; 120 | if ((entry_text == "")) 121 | { 122 | return; 123 | } 124 | 125 | /* Find section number, 0 is "All Sections" */ 126 | var section_num = this.section_list.section_combo.get_active(); 127 | if (section_num >= 1) 128 | { 129 | //print(@"add section $(section_num)!\n"); 130 | entry_text = entry_text + @".$(section_num)"; 131 | } 132 | 133 | this.last_entry_text = entry_text; 134 | entry_data = entry_text.split("."); 135 | 136 | if (entry_data.length == 1) 137 | { 138 | for (var ii = 1; ii<(this.app.section_num_max+1); ii++) 139 | { 140 | man_uri_test[ii-1] = new man_uri(entry_text,ii.to_string()); 141 | 142 | thread[ii-1] = new Thread("man"+ii.to_string()+"_uri_exist", man_uri_test[ii-1].man_uri_exist); 143 | } 144 | 145 | for (var ii = 1; ii<(this.app.section_num_max+1); ii++) 146 | { 147 | status[ii-1] = thread[ii-1].join(); 148 | //print("Inner, the status is "+status[ii-1].to_string()+"\n"); 149 | 150 | if (status[ii-1]) 151 | { 152 | this.view_current.load_uri(man_uri_test[ii-1].uri); 153 | entry_found = true; 154 | break; 155 | } 156 | } 157 | } 158 | else if (entry_data.length > 1) 159 | { 160 | man_uri_test[0] = new man_uri(entry_data[0],entry_data[1]); 161 | 162 | status[0] = man_uri_test[0].man_uri_exist(); 163 | 164 | if (status[0]) 165 | { 166 | this.view_current.load_uri(man_uri_test[0].uri); 167 | entry_found = true; 168 | } 169 | } 170 | 171 | if (!entry_found) 172 | { 173 | // FIXME: should limit the time for showing this tooltip 174 | this.set_tooltip_text("No man page for "+entry_text); 175 | this.trigger_tooltip_query(); 176 | } 177 | else 178 | { 179 | this.set_has_tooltip(false); 180 | 181 | if ((this.search_list!=null)&&(this.search_list.get_realized())) 182 | { 183 | this.search_list.destroy(); 184 | } 185 | } 186 | 187 | if ((this.prefer_dialog == null) || (!this.prefer_dialog.get_realized())) 188 | { 189 | this.prefer_dialog = new PreferDialog(this); 190 | } 191 | 192 | this.prefer_dialog.view = this.view_current; 193 | this.prefer_dialog.hide(); 194 | /* Add a 50 ms delay */ 195 | Timeout.add(50,()=>{this.prefer_dialog.update_page_prefer();return Source.REMOVE;}); 196 | } 197 | 198 | [GtkCallback] 199 | private void on_btn_add_page_clicked (Gtk.Button self) 200 | { 201 | this.pager.append_manpage(); 202 | //this.pager.show_all(); 203 | } 204 | 205 | [GtkCallback] 206 | private void on_entry_search_changed (Gtk.SearchEntry self) 207 | { 208 | string text = self.get_text(); 209 | int long_search_char = this.prefer.search_char_no; 210 | //print(@"long:$(long_search_char)\n"); 211 | bool enable_search = btn_enable_search.get_active(); 212 | KeywordList old_list; 213 | 214 | if (enable_search && (text.length >= long_search_char)) 215 | { 216 | old_list = this.search_list; 217 | 218 | if ((old_list != null) && (old_list.get_realized())) 219 | { 220 | Timeout.add(150,()=>{old_list.destroy();return Source.REMOVE;}); // add a 150 ms delay 221 | } 222 | 223 | this.search_list = new KeywordList(this,text); 224 | 225 | if (this.search_list.find_num > 0) 226 | { 227 | this.search_list.show_all(); 228 | this.search_list.update_keyword_list_pos(this); 229 | this.present(); 230 | } 231 | } 232 | else 233 | { 234 | if ((this.search_list != null) && (this.search_list.get_realized())) 235 | { 236 | this.search_list.destroy(); 237 | } 238 | } 239 | 240 | } 241 | 242 | [GtkCallback] 243 | private void on_entry_search_enter (Gtk.Entry self) 244 | { 245 | btn_man.clicked(); 246 | } 247 | 248 | [GtkCallback] 249 | private void on_quit_clicked (Gtk.MenuItem self) 250 | { 251 | this.destroy(); 252 | } 253 | 254 | [GtkCallback] 255 | private void on_about_clicked (Gtk.MenuItem self) 256 | { 257 | AboutDialog about_dialog; 258 | 259 | about_dialog=new AboutDialog(); 260 | about_dialog.show_all(); 261 | } 262 | 263 | [GtkCallback] 264 | private void on_copy_clicked (Gtk.MenuItem self) 265 | { 266 | var focus = this.get_focus(); 267 | 268 | if (focus == this.view_current) 269 | { 270 | this.view_current.execute_editing_command("Copy"); 271 | } 272 | else if (focus == this.entry_search) 273 | { 274 | this.entry_search.copy_clipboard(); 275 | } 276 | } 277 | 278 | [GtkCallback] 279 | private async void on_save_as_clicked (Gtk.MenuItem self) 280 | { 281 | File file = null;; 282 | int save_resp; 283 | string filepath; 284 | Regex regex_html; 285 | bool save_succeed = false; 286 | string file_extension = ".mhtml"; 287 | string title = this.view_current.title; 288 | 289 | try 290 | { 291 | regex_html = new Regex("^[\\S\\s]+"+file_extension+"?$"); 292 | //page_input = yield view.save(WebKit.SaveMode.MHTML); 293 | } 294 | catch (Error e) 295 | { 296 | message(e.message+"\n"); 297 | 298 | return; 299 | } 300 | 301 | if (this.file_chooser == null) 302 | { 303 | this.file_chooser = new Gtk.FileChooserDialog("Save as",null,Gtk.FileChooserAction.SAVE); 304 | } 305 | 306 | if (this.last_entry_text!="") 307 | { 308 | this.file_chooser.set_current_name(this.last_entry_text+file_extension); 309 | } 310 | else 311 | { 312 | this.file_chooser.set_current_name((title??"new")+file_extension); 313 | } 314 | 315 | save_resp = this.file_chooser.run(); 316 | //print(save_resp.to_string()+"\n"); 317 | 318 | if (save_resp == Gtk.ResponseType.ACCEPT) 319 | { 320 | filepath = this.file_chooser.get_filename(); 321 | 322 | file = File.new_for_path(filepath); 323 | 324 | var parent_path = file.get_parent().get_parse_name(); 325 | var name = file.get_basename(); 326 | //file.get_basename() 327 | if (!regex_html.match(name)) 328 | name = name+file_extension; // add .html file extension 329 | 330 | file = File.new_build_filename(parent_path,name); 331 | 332 | try 333 | { 334 | save_succeed = yield this.view_current.save_to_file(file,WebKit.SaveMode.MHTML,null); 335 | } 336 | catch (Error e) 337 | { 338 | message(e.message); 339 | 340 | return; 341 | } 342 | 343 | } 344 | 345 | this.file_chooser.hide(); 346 | } 347 | 348 | [GtkCallback] 349 | private void on_prefer_clicked (Gtk.MenuItem self) 350 | { 351 | if ((this.prefer_dialog == null) || (!this.prefer_dialog.get_realized())) 352 | { 353 | this.prefer_dialog = new PreferDialog(this); 354 | this.prefer_dialog.show_all(); 355 | } 356 | else 357 | { 358 | this.prefer_dialog.present(); 359 | } 360 | } 361 | 362 | [GtkCallback] 363 | private void on_find_clicked (Gtk.MenuItem self) 364 | { 365 | if ((this.search_dialog==null)||(!this.search_dialog.get_realized())) 366 | { 367 | this.search_dialog = new SearchDialog(this); 368 | this.search_dialog.show_all(); 369 | } 370 | else 371 | { 372 | this.search_dialog.present(); 373 | } 374 | } 375 | 376 | public class man_uri 377 | { 378 | public string entry_text {set;get;default="man";} 379 | public string sec_num {set;get;default="1";} 380 | public string uri; 381 | 382 | public man_uri(string entry_text,string sec_num) 383 | { 384 | this.uri = "http://localhost/cgi-bin/man/man2html?"+sec_num.strip()+"+"+entry_text.strip(); 385 | } 386 | 387 | public bool man_uri_exist() 388 | { 389 | Soup.Session session; 390 | Soup.Message message; 391 | uint status_code; 392 | 393 | session = new Soup.Session(); 394 | message = new Soup.Message("HEAD",this.uri); 395 | session.send_message(message); 396 | 397 | status_code=message.status_code; 398 | 399 | //print(@"status code: $(status_code)\n"); 400 | 401 | if (status_code<400) 402 | { 403 | return true; 404 | } 405 | else 406 | { 407 | return false; 408 | } 409 | } 410 | } 411 | 412 | [GtkCallback] 413 | void on_btn_back_clicked (Gtk.Button self) 414 | { 415 | this.view_current.go_back(); 416 | } 417 | 418 | [GtkCallback] 419 | void on_btn_fwd_clicked (Gtk.Button self) 420 | { 421 | this.view_current.go_forward(); 422 | } 423 | 424 | [GtkCallback] 425 | void on_btn_home_clicked (Gtk.Button self) 426 | { 427 | this.view_current.load_uri(this.home_uri); 428 | } 429 | 430 | [GtkCallback] 431 | void on_btn_add_bookmark_clicked (Gtk.Button self) 432 | { 433 | string title; 434 | string uri; 435 | 436 | title = this.view_current.get_title()??"NULL"; 437 | uri = this.view_current.get_uri(); 438 | 439 | try 440 | { 441 | //print(@"VALUES (\"$(title)\", \"$(uri)\")\n"); 442 | if (uri!=null) 443 | { 444 | var bookmarks_db = this.app.bookmarks_db; 445 | bookmarks_db.run_query(@"REPLACE INTO bookmarks (title, uri) VALUES (\"$(title)\", \"$(uri)\")"); 446 | } 447 | } 448 | catch (Error e) 449 | { 450 | message(e.message); 451 | } 452 | } 453 | 454 | [GtkCallback] 455 | void on_btn_bookmarks_clicked (Gtk.Button self) 456 | { 457 | if ((this.bookmarks_dialog == null)||(!this.bookmarks_dialog.get_realized())) 458 | { 459 | this.bookmarks_dialog = new BookmarksDialog(this); 460 | this.bookmarks_dialog.show_all(); 461 | } 462 | else 463 | { 464 | this.bookmarks_dialog.show_all(); 465 | return; 466 | } 467 | 468 | } 469 | } 470 | } -------------------------------------------------------------------------------- /src/MultiTabPager.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 XX Wu 3 | * 4 | * This file is part of Man Helper 5 | * 6 | * Man Helper is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * Man Helper is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * You should have received a copy of the GNU General Public License 15 | * along with Man Helper. If not, see . 16 | * 17 | * Authored by: XX Wu 18 | */ 19 | 20 | namespace ManHelper 21 | { 22 | /* Add multitabs using scrolled window. */ 23 | [GtkTemplate (ui = "/ui/multitab_pager.ui")] 24 | internal class MultitabPager: Gtk.Notebook 25 | { 26 | MainWin win; 27 | string page_no = "page no."; 28 | 29 | [GtkChild] 30 | internal unowned Gtk.ScrolledWindow first_scrolled; 31 | [GtkChild] 32 | internal unowned Gtk.Label first_label; 33 | [GtkChild] 34 | internal unowned Gtk.Button first_btn_close_page; 35 | [GtkChild] 36 | internal unowned Gtk.Image image_close; 37 | 38 | public int n_pages {get {return this.get_n_pages();}} 39 | 40 | public MultitabPager(MainWin win) 41 | { 42 | WebKit.WebView view; 43 | this.win = win; 44 | win.pager = this; 45 | view = new WebKit.WebView(); 46 | this.first_scrolled.add(view); 47 | win.view_current = view; 48 | 49 | view.set_data("button",first_btn_close_page); 50 | first_btn_close_page.set_data("label",first_label); 51 | first_scrolled.set_data("view",view); 52 | 53 | view.load_changed.connect(on_view_load_finished); 54 | view.button_press_event.connect(win.on_search_list_outside_mouse_press); 55 | } 56 | 57 | public void append_manpage() 58 | { 59 | var new_scrolled = new Gtk.ScrolledWindow(null,null); 60 | var label_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL,0); 61 | this.append_page(new_scrolled,label_box); 62 | 63 | var page_label = new Gtk.Label(page_label_text(this.n_pages)); 64 | var btn_page_close = new Gtk.Button(); 65 | btn_page_close.set_data("label",page_label); 66 | var image_dup = new Gtk.Image(); 67 | 68 | string icon_name; 69 | Gtk.IconSize icon_size; 70 | image_close.get_icon_name(out icon_name,out icon_size); 71 | image_dup.set_from_icon_name(icon_name,icon_size); 72 | btn_page_close.set_image(image_dup); 73 | 74 | btn_page_close.set_relief(Gtk.ReliefStyle.NONE); 75 | label_box.pack_start(page_label,false,true,0); 76 | label_box.pack_start(btn_page_close,false,true,0); 77 | btn_page_close.set_data(page_no,this.n_pages); 78 | btn_page_close.clicked.connect(on_btn_page_close_clicked); 79 | 80 | var new_view = new WebKit.WebView(); 81 | new_view.set_data("button",btn_page_close); 82 | new_view.load_changed.connect(on_view_load_finished); 83 | new_view.button_press_event.connect(win.on_search_list_outside_mouse_press); 84 | //win.view += new_view; /* Dynamically increase the view array */ 85 | //new_view.button_press_event.connect(on_view_mouse_press); 86 | //new_scrolled.add_with_properties(new_view); 87 | new_scrolled.add(new_view); 88 | new_scrolled.set_data("view",new_view); 89 | new_scrolled.set_data("button",btn_page_close); 90 | 91 | new_scrolled.show_all(); 92 | label_box.show_all(); 93 | 94 | this.set_current_page(this.n_pages-1); 95 | /* Notify that we have switched page */ 96 | this.switch_page(new_scrolled,this.n_pages-1); 97 | 98 | /* Set page zoom ratio accordingly */ 99 | double ratio; 100 | uint32 font_size_new; 101 | var settings = new_view.get_settings(); 102 | var page_zoomer = this.win.page_zoomer; 103 | ratio = int.parse(page_zoomer.entry_zoom.get_text())/100.0; 104 | // font_size_new = (uint32)(Math.round(this.win.init_font_size*ratio)); 105 | font_size_new = (uint32)(Math.round(this.win.prefer.font_size*ratio)); 106 | 107 | settings.set_default_font_size(font_size_new); 108 | new_view.set_settings(settings); 109 | } 110 | 111 | public void on_view_load_finished(WebKit.WebView self, WebKit.LoadEvent load_event) 112 | { 113 | if (load_event == WebKit.LoadEvent.FINISHED) 114 | { 115 | //print("Load finished!\n"); 116 | 117 | Gtk.Button btn_page_close = self.get_data("button"); 118 | Gtk.Label page_label = btn_page_close.get_data("label"); 119 | 120 | var title_page = self.get_title().replace("Man page of ",""); 121 | page_label.set_text(title_page); 122 | 123 | if ((win.prefer_dialog == null) || (!win.prefer_dialog.get_realized())) 124 | { 125 | this.win.prefer_dialog = new PreferDialog(this.win); 126 | } 127 | } 128 | } 129 | 130 | public void on_btn_page_close_clicked(Gtk.Button self) 131 | { 132 | var page_index = self.get_data(page_no); 133 | 134 | /* Update page_no of each button after the closed page */ 135 | for(var ii=page_index;ii(page_no,ii); /* Decrease the page_no by 1 */ 140 | Gtk.Label page_label = btn_close.get_data("label"); 141 | 142 | var label = page_label.get_text(); 143 | var pattern = "^Page [0-9]+$"; 144 | if (Regex.match_simple(pattern,label)) 145 | { 146 | page_label.set_text(page_label_text(ii)); 147 | } 148 | } 149 | 150 | this.remove_page(page_index-1); 151 | } 152 | 153 | private string page_label_text(uint index) 154 | { 155 | return "Page "+index.to_string(); 156 | } 157 | 158 | [GtkCallback] 159 | private void on_page_switched(Gtk.Widget page,uint page_num) 160 | { 161 | win.view_current = page.get_data("view"); 162 | 163 | win.page_zoomer.update_view_zoom(); 164 | 165 | if ((win.prefer_dialog == null) || (!win.prefer_dialog.get_realized())) 166 | { 167 | this.win.prefer_dialog = new PreferDialog(this.win); 168 | } 169 | 170 | win.prefer_dialog.view = win.view_current; 171 | win.prefer_dialog.hide(); 172 | 173 | //Timeout.add(100,()=>{win.prefer_dialog.update_page_prefer();return Source.REMOVE;}); 174 | //win.view_current.reload(); 175 | win.prefer_dialog.update_page_prefer(); 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /src/PageZoomer.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 XX Wu 3 | * 4 | * This file is part of Man Helper 5 | * 6 | * Man Helper is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * Man Helper is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * You should have received a copy of the GNU General Public License 15 | * along with Man Helper. If not, see . 16 | * 17 | * Authored by: XX Wu 18 | */ 19 | 20 | namespace ManHelper 21 | { 22 | /* This is the page zommer of the main window. */ 23 | [GtkTemplate (ui = "/ui/page_zoomer.ui")] 24 | internal class PageZoomer: Gtk.Box 25 | { 26 | public int zoom_ratio {set;get;default=100;} 27 | public int zoom_step {set;get;default=10;} 28 | 29 | private static int zoom_min = 10; 30 | private static int zoom_max = 990; 31 | //internal uint32 default_font_size; 32 | 33 | private MainWin win; 34 | 35 | [GtkChild] 36 | internal unowned Gtk.Entry entry_zoom; 37 | 38 | /* 39 | [GtkChild] 40 | Gtk.Button btn_up; 41 | [GtkChild] 42 | Gtk.Button btn_down; 43 | [GtkChild] 44 | Gtk.Button btn_fit; 45 | */ 46 | public PageZoomer(MainWin win) 47 | { 48 | this.win = win; 49 | //var view = win.view_current; 50 | //var settings = view.get_settings(); 51 | //this.default_font_size = settings.get_default_font_size(); 52 | } 53 | 54 | [GtkCallback] 55 | private void on_btn_up_clicked(Gtk.Button self) 56 | { 57 | this.zoom_ratio = int.min(this.zoom_ratio+this.zoom_step,PageZoomer.zoom_max); 58 | update_zoom_entry(); 59 | } 60 | 61 | [GtkCallback] 62 | private void on_btn_down_clicked(Gtk.Button self) 63 | { 64 | this.zoom_ratio = int.max(this.zoom_ratio-this.zoom_step,PageZoomer.zoom_min); 65 | update_zoom_entry(); 66 | } 67 | 68 | [GtkCallback] 69 | private void on_btn_fit_clicked(Gtk.Button self) 70 | { 71 | this.zoom_ratio = 100; 72 | update_zoom_entry(); 73 | } 74 | 75 | internal void update_zoom_entry() 76 | { 77 | this.entry_zoom.set_text(this.zoom_ratio.to_string()); 78 | 79 | update_view_zoom(); 80 | } 81 | 82 | [GtkCallback] 83 | private void on_entry_zoom_changed(Gtk.Editable self) 84 | { 85 | uint interval = 100; 86 | 87 | Timeout.add(interval,()=>{update_view_zoom();return Source.REMOVE;}); 88 | } 89 | 90 | internal void update_view_zoom() 91 | { 92 | double ratio; 93 | //uint32 default_font_size; 94 | uint32 font_size_new; 95 | int zoom_ratio_raw; 96 | 97 | var view = this.win.view_current; 98 | var settings = view.get_settings(); 99 | 100 | zoom_ratio_raw = int.parse(this.entry_zoom.get_text()); 101 | zoom_ratio = zoom_ratio_raw.clamp(PageZoomer.zoom_min,PageZoomer.zoom_max); /* update zoom ratio */ 102 | ratio = zoom_ratio/100.0; 103 | //print("ratio:%d\n", zoom_ratio); 104 | // font_size_new = (uint32)(Math.round(this.win.init_font_size*ratio)); 105 | font_size_new = (uint32)(Math.round(this.win.prefer.font_size*ratio)); 106 | //print("new font size:%f\n",font_size_new); 107 | settings.set_default_font_size(font_size_new); 108 | view.set_settings(settings); 109 | 110 | int prefer_font_size = (int)(font_size_new*Pango.SCALE); 111 | //this.win.prefer_font_desc.set_size(prefer_font_size); 112 | this.win.prefer.font_desc.set_size(prefer_font_size); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /src/PreferDialog.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 XX Wu 3 | * 4 | * This file is part of Man Helper 5 | * 6 | * Man Helper is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * Man Helper is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * You should have received a copy of the GNU General Public License 15 | * along with Man Helper. If not, see . 16 | * 17 | * Authored by: XX Wu 18 | */ 19 | 20 | namespace ManHelper 21 | { 22 | /* This is the preferences dialog. */ 23 | [GtkTemplate (ui = "/ui/prefer_dialog.ui")] 24 | public class PreferDialog: Gtk.Dialog 25 | { 26 | internal MainWin win = null; 27 | internal WebKit.WebView view = null; 28 | internal ThemeDialog theme_dialog = null; 29 | internal Preferences prefer = null; 30 | 31 | [GtkChild] 32 | internal unowned Gtk.FontButton btn_font; 33 | [GtkChild] 34 | internal unowned Gtk.ColorButton btn_backcolor; 35 | [GtkChild] 36 | internal unowned Gtk.Button btn_apply; 37 | [GtkChild] 38 | internal unowned Gtk.CheckButton btn_startup; 39 | [GtkChild] 40 | private unowned Gtk.Entry entry_search_char_no; 41 | 42 | 43 | public PreferDialog (MainWin win) 44 | { 45 | this.win = win; 46 | this.view = win.view_current; 47 | 48 | this.prefer = win.prefer ?? new Preferences(win); 49 | //var settings = view.get_settings(); 50 | //prefer_load_settings(settings); 51 | init_preferdialog(this.prefer); 52 | win.prefer = this.prefer; 53 | //this.default_font_size = settings.get_default_font_size(); 54 | } 55 | 56 | /* load current settings to the preferences dialog */ 57 | /* 58 | private void prefer_load_settings (WebKit.Settings settings) 59 | { 60 | var default_font_size = settings.get_default_font_size(); 61 | var default_font_family = settings.get_default_font_family(); 62 | var default_backcolor = this.view.get_background_color(); 63 | //print("fake: "+default_font_family+"\n"); 64 | if (this.win.init_font_size==0) 65 | { 66 | this.win.init_font_size = default_font_size; 67 | } 68 | 69 | try 70 | { 71 | string fc_stdout; 72 | string fc_stderr; 73 | int fc_status; 74 | 75 | string fc_cmd = @"fc-match \"$(default_font_family)\""; 76 | Process.spawn_command_line_sync (fc_cmd, out fc_stdout, out fc_stderr, out fc_status); 77 | 78 | var fc_output = fc_stdout.split("\""); 79 | 80 | if (fc_output.length>1) 81 | { 82 | default_font_family = fc_output[1]; 83 | 84 | if (this.win.init_font_family == null) 85 | { 86 | this.win.init_font_family = default_font_family; 87 | } 88 | //print("real: "+default_font_family+"\n"); 89 | } 90 | } 91 | catch (SpawnError e) 92 | { 93 | message(e.message); 94 | } 95 | 96 | var font_desc = new Pango.FontDescription(); 97 | 98 | font_desc.set_family(default_font_family); 99 | font_desc.set_size((int)default_font_size*Pango.SCALE); 100 | 101 | btn_font.set_font_desc(font_desc); 102 | // need work on theme button 103 | 104 | btn_backcolor.set_rgba(default_backcolor); 105 | } 106 | */ 107 | 108 | public void init_preferdialog (Preferences prefer) 109 | { 110 | btn_font.set_font_desc(prefer.font_desc); 111 | btn_backcolor.set_rgba(prefer.back_color); 112 | entry_search_char_no.set_text(prefer.search_char_no.to_string()); 113 | //print(prefer.search_char_no.to_string()); 114 | } 115 | 116 | [GtkCallback] 117 | private void on_btn_theme_clicked (Gtk.Button self) 118 | { 119 | if ((this.theme_dialog==null)||(!this.theme_dialog.get_realized())) 120 | { 121 | this.theme_dialog = new ThemeDialog(this,this.view); 122 | 123 | this.theme_dialog.show_all(); 124 | } 125 | else 126 | { 127 | this.theme_dialog.present(); 128 | } 129 | } 130 | 131 | [GtkCallback] 132 | private void on_prefer_btn_reset_clicked (Gtk.Button self) 133 | { 134 | Gdk.RGBA init_backcolor = {}; 135 | 136 | var init_font_desc = new Pango.FontDescription(); 137 | var default_font_family = this.win.default_prefer.font_family; 138 | var default_font_size = this.win.default_prefer.font_size; 139 | 140 | /* Set init font and size to default preferences */ 141 | if (default_font_family != null) 142 | { 143 | init_font_desc.set_family(default_font_family); 144 | } 145 | if (default_font_size > 0) 146 | { 147 | init_font_desc.set_size((int)default_font_size*Pango.SCALE); 148 | } 149 | 150 | btn_font.set_font_desc(init_font_desc); 151 | 152 | init_backcolor.parse("rgb(255,255,255)"); /* back to white */ 153 | btn_backcolor.set_rgba(init_backcolor); 154 | entry_search_char_no.set_text(Preferences.search_char_no_default.to_string()); 155 | btn_apply.clicked(); 156 | } 157 | 158 | [GtkCallback] 159 | public void on_prefer_btn_apply_clicked (Gtk.Button self) 160 | { 161 | var font_desc = this.btn_font.get_font_desc(); 162 | var backcolor = this.btn_backcolor.get_rgba(); 163 | var char_no = int.parse(this.entry_search_char_no.get_text()); 164 | /* Store all prefer settings */ 165 | this.prefer.font_desc = font_desc.copy(); 166 | this.prefer.back_color = backcolor; 167 | this.prefer.search_char_no = char_no; 168 | //this.win.search_chars_length = chars_no; 169 | update_page_prefer(); 170 | } 171 | 172 | public void update_page_prefer () 173 | { 174 | var settings = this.view.get_settings(); 175 | //var font_desc = this.win.prefer_font_desc; 176 | //var backcolor = this.win.prefer_backcolor; 177 | var font_desc = this.prefer.font_desc.copy(); 178 | var backcolor = this.prefer.back_color; 179 | 180 | var font_size = font_desc.get_size(); 181 | var font_family = font_desc.get_family(); 182 | //print(@"size:$(font_size)\n"); 183 | settings.set_default_font_size(font_size/Pango.SCALE); 184 | settings.set_default_font_family(font_family); 185 | 186 | /* Update page zoomer */ 187 | double font_size_scaled = font_size/Pango.SCALE*1.0; /* ensure it is of double type */ 188 | // this.win.page_zoomer.zoom_ratio = (int)Math.round(font_size_scaled/this.win.init_font_size*100); 189 | this.win.page_zoomer.zoom_ratio = (int)Math.round(font_size_scaled/this.win.prefer.font_size*100); 190 | this.win.page_zoomer.update_zoom_entry(); 191 | 192 | this.view.set_background_color(backcolor); 193 | 194 | /* Check whether change startup options */ 195 | if (btn_startup.get_active()) 196 | { 197 | App app = this.win.app; 198 | save_startup_options(app); 199 | } 200 | 201 | if (this.win.prefer_theme_CSS != null) 202 | { 203 | this.win.prefer_theme_CSS.set_theme_CSS(this.view); 204 | } 205 | 206 | } 207 | 208 | private void save_startup_options (App app) 209 | { 210 | var builder = new Json.Builder(); 211 | 212 | var font_desc = this.btn_font.get_font_desc(); 213 | var font_size = font_desc.get_size(); 214 | var font_family = font_desc.get_family(); 215 | var backcolor = this.btn_backcolor.get_rgba(); 216 | var search_char_no = this.entry_search_char_no.get_text(); 217 | 218 | builder.begin_object (); 219 | builder.set_member_name ("font-family"); 220 | builder.add_string_value (font_family.to_string()); 221 | 222 | builder.set_member_name ("font-size"); 223 | builder.add_string_value ((font_size/Pango.SCALE).to_string()); 224 | 225 | builder.set_member_name ("background"); 226 | builder.add_string_value (backcolor.to_string()); 227 | 228 | builder.set_member_name ("search-char-no"); 229 | builder.add_string_value (search_char_no.to_string()); 230 | 231 | var theme_CSS = this.win.prefer_theme_CSS; 232 | if (theme_CSS == null) 233 | { 234 | var black = "rgb(0,0,0)"; 235 | 236 | builder.set_member_name ("theme-title"); 237 | builder.add_string_value (black); 238 | builder.set_member_name ("theme-heading"); 239 | builder.add_string_value (black); 240 | builder.set_member_name ("theme-regular"); 241 | builder.add_string_value (black); 242 | builder.set_member_name ("theme-bold"); 243 | builder.add_string_value (black); 244 | builder.set_member_name ("theme-italic"); 245 | builder.add_string_value (black); 246 | } 247 | else 248 | { 249 | builder.set_member_name ("theme-title"); 250 | builder.add_string_value (theme_CSS.title_rgba.to_string()); 251 | builder.set_member_name ("theme-heading"); 252 | builder.add_string_value (theme_CSS.heading_rgba.to_string()); 253 | builder.set_member_name ("theme-regular"); 254 | builder.add_string_value (theme_CSS.regular_rgba.to_string()); 255 | builder.set_member_name ("theme-bold"); 256 | builder.add_string_value (theme_CSS.bold_rgba.to_string()); 257 | builder.set_member_name ("theme-italic"); 258 | builder.add_string_value (theme_CSS.italic_rgba.to_string()); 259 | } 260 | 261 | builder.end_object (); 262 | 263 | var generator = new Json.Generator(); 264 | var root = builder.get_root(); 265 | 266 | generator.set_root(root); 267 | 268 | /* Store in the same directory with bookmarks */ 269 | var bookmarks_dirpath = app.bookmarks_parent_dir+app.bookmarks_directory; 270 | var startup_filepath = Path.build_filename(bookmarks_dirpath,app.startup_filename); 271 | 272 | try 273 | { 274 | generator.to_file(startup_filepath); 275 | } 276 | catch (Error e) 277 | { 278 | message(e.message); 279 | } 280 | } 281 | 282 | internal void load_startup_options (App app) 283 | { 284 | var parser = new Json.Parser(); 285 | 286 | var bookmarks_dirpath = app.bookmarks_parent_dir+app.bookmarks_directory; 287 | var startup_filepath = Path.build_filename(bookmarks_dirpath,app.startup_filename); 288 | 289 | if (FileUtils.test(startup_filepath,FileTest.EXISTS)) 290 | { 291 | try 292 | { 293 | parser.load_from_file(startup_filepath); 294 | 295 | var root = parser.get_root(); 296 | var obj = root.get_object(); 297 | 298 | var font_family = obj.get_member("font-family").get_string(); 299 | var font_size_str = obj.get_member("font-size").get_string(); 300 | var backcolor_str = obj.get_member("background").get_string(); 301 | var search_char_no_str = obj.get_member("search-char-no").get_string(); 302 | //print("%s\n%s\n%s\n",font_family,font_size,backcolor); 303 | //print("search: %s\n",search_char_no_str); 304 | var font_size = int.parse(font_size_str); 305 | 306 | var font_desc = new Pango.FontDescription(); 307 | font_desc.set_family(font_family); 308 | font_desc.set_size((int)font_size*Pango.SCALE); 309 | 310 | btn_font.set_font_desc(font_desc); 311 | 312 | Gdk.RGBA backcolor = {}; 313 | backcolor.parse(backcolor_str); 314 | btn_backcolor.set_rgba(backcolor); 315 | 316 | entry_search_char_no.set_text(search_char_no_str); 317 | 318 | /* Load theme colors */ 319 | var theme_title_str = obj.get_member("theme-title").get_string(); 320 | var theme_heading_str = obj.get_member("theme-heading").get_string(); 321 | var theme_regular_str = obj.get_member("theme-regular").get_string(); 322 | var theme_bold_str = obj.get_member("theme-bold").get_string(); 323 | var theme_italic_str = obj.get_member("theme-italic").get_string(); 324 | //print("%p\n",this.win.theme_CSS); 325 | if (this.win.prefer_theme_CSS == null) 326 | { 327 | var startup_theme_CSS = new ThemeCSS(); 328 | startup_theme_CSS.prefer_dialog = this; 329 | startup_theme_CSS.title_rgba.parse(theme_title_str); 330 | startup_theme_CSS.heading_rgba.parse(theme_heading_str); 331 | startup_theme_CSS.regular_rgba.parse(theme_regular_str); 332 | startup_theme_CSS.bold_rgba.parse(theme_bold_str); 333 | startup_theme_CSS.italic_rgba.parse(theme_italic_str); 334 | 335 | this.win.prefer_theme_CSS = startup_theme_CSS; 336 | } 337 | btn_apply.clicked(); 338 | } 339 | catch (Error e) 340 | { 341 | message(e.message); 342 | } 343 | } 344 | else 345 | { 346 | return; 347 | } 348 | } 349 | } 350 | 351 | /* Add text color theme dialog */ 352 | [GtkTemplate (ui = "/ui/theme_dialog.ui")] 353 | internal class ThemeDialog: Gtk.Dialog 354 | { 355 | internal PreferDialog prefer_dialog = null; 356 | internal WebKit.WebView view = null; 357 | 358 | [GtkChild] 359 | internal unowned Gtk.ColorButton btn_title; 360 | [GtkChild] 361 | internal unowned Gtk.ColorButton btn_heading; 362 | [GtkChild] 363 | internal unowned Gtk.ColorButton btn_regular; 364 | [GtkChild] 365 | internal unowned Gtk.ColorButton btn_bold; 366 | [GtkChild] 367 | internal unowned Gtk.ColorButton btn_italic; 368 | 369 | public ThemeDialog(PreferDialog prefer_dialog, WebKit.WebView view) 370 | { 371 | this.prefer_dialog = prefer_dialog; 372 | this.view = view; 373 | 374 | theme_load_settings(view); 375 | } 376 | 377 | private void theme_load_settings (WebKit.WebView view) 378 | { 379 | var theme_CSS = this.prefer_dialog.win.prefer_theme_CSS; 380 | 381 | if (theme_CSS != null) 382 | { 383 | //print("load theme\n"); 384 | var title_rgba = theme_CSS.title_rgba; 385 | var heading_rgba = theme_CSS.heading_rgba; 386 | var regular_rgba = theme_CSS.regular_rgba; 387 | var bold_rgba = theme_CSS.bold_rgba; 388 | var italic_rgba = theme_CSS.italic_rgba; 389 | 390 | btn_title.set_rgba(title_rgba); 391 | btn_heading.set_rgba(heading_rgba); 392 | btn_regular.set_rgba(regular_rgba); 393 | btn_bold.set_rgba(bold_rgba); 394 | btn_italic.set_rgba(italic_rgba); 395 | } 396 | } 397 | 398 | [GtkCallback] 399 | private void on_theme_btn_ok_clicked (Gtk.Button self) 400 | { 401 | var theme_CSS = new ThemeCSS.from_theme(this); 402 | //print(theme_css.to_string()); 403 | 404 | this.prefer_dialog.win.prefer_theme_CSS = theme_CSS; 405 | this.hide(); 406 | } 407 | } 408 | 409 | public class Preferences 410 | { 411 | private MainWin win; 412 | private int _font_size; 413 | private string _font_family; 414 | private Pango.FontDescription _font_desc; 415 | 416 | public int font_size 417 | { 418 | get {return _font_size;} 419 | set 420 | { 421 | _font_size = value; 422 | 423 | if (font_desc!=null) 424 | { 425 | font_desc.set_size((int)font_size*Pango.SCALE); 426 | } 427 | } 428 | } 429 | public string font_family 430 | { 431 | get {return _font_family;} 432 | set 433 | { 434 | _font_family = value; 435 | 436 | if (font_desc!=null) 437 | { 438 | font_desc.set_family(_font_family); 439 | } 440 | } 441 | } 442 | public Pango.FontDescription font_desc 443 | { 444 | get 445 | { 446 | _font_desc = new Pango.FontDescription(); 447 | _font_desc.set_family(font_family); 448 | _font_desc.set_size((int)font_size*Pango.SCALE); 449 | return _font_desc; 450 | } 451 | set 452 | { 453 | _font_desc = value.copy(); 454 | _font_family = _font_desc.get_family(); 455 | _font_size = _font_desc.get_size()/Pango.SCALE; 456 | } 457 | } 458 | 459 | public Gdk.RGBA back_color {set;get;} 460 | public static int search_char_no_default = 6; 461 | public int search_char_no {set;get;} 462 | 463 | public Preferences (MainWin win) 464 | { 465 | this.win = win; 466 | //this.win.prefer = this; 467 | 468 | var view = win.view_current; 469 | var settings = view.get_settings(); 470 | 471 | var default_font_size = settings.get_default_font_size(); 472 | var default_font_family = settings.get_default_font_family(); /* just placeholder */ 473 | var default_backcolor = view.get_background_color(); 474 | 475 | /* Get font size from webview if necessary 476 | if (this.win.prefer.font_size == 0) 477 | { 478 | this.win.prefer.font_size = (int)default_font_size; 479 | }*/ 480 | 481 | try 482 | { 483 | string fc_stdout; 484 | string fc_stderr; 485 | int fc_status; 486 | 487 | string fc_cmd = @"fc-match \"$(default_font_family)\""; 488 | Process.spawn_command_line_sync (fc_cmd, out fc_stdout, out fc_stderr, out fc_status); 489 | 490 | var fc_output = fc_stdout.split("\""); 491 | 492 | if (fc_output.length > 1) 493 | { 494 | default_font_family = fc_output[1]; /* Real default font faimly */ 495 | this.font_family = default_font_family; 496 | //print(default_font_family+"\n"); 497 | 498 | /* Get font faimly from webview if necessary 499 | if (this.win.prefer.font_family == null) 500 | { 501 | this.win.prefer.font_family = default_font_family; 502 | }*/ 503 | } 504 | } 505 | catch (SpawnError e) 506 | { 507 | message(e.message); 508 | } 509 | 510 | /* Get preferences from webview */ 511 | font_size = (int)default_font_size; 512 | font_family= default_font_family; 513 | back_color = default_backcolor; 514 | search_char_no = search_char_no_default; 515 | 516 | /* 517 | if (this.win.search_chars_length==0) 518 | { 519 | this.win.search_chars_length = search_chars_no; 520 | } 521 | else 522 | { 523 | search_chars_no = this.win.search_chars_length; 524 | }*/ 525 | } 526 | } 527 | } -------------------------------------------------------------------------------- /src/SearchDialog.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 XX Wu 3 | * 4 | * This file is part of Man Helper 5 | * 6 | * Man Helper is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * Man Helper is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * You should have received a copy of the GNU General Public License 15 | * along with Man Helper. If not, see . 16 | * 17 | * Authored by: XX Wu 18 | */ 19 | 20 | namespace ManHelper 21 | { 22 | /* This is the search dialog. */ 23 | [GtkTemplate (ui = "/ui/search_dialog.ui")] 24 | public class SearchDialog: Gtk.Dialog 25 | { 26 | [GtkChild] 27 | private unowned Gtk.Entry entry_find; 28 | [GtkChild] 29 | private unowned Gtk.CheckButton option_case; 30 | [GtkChild] 31 | private unowned Gtk.CheckButton option_wrap; 32 | [GtkChild] 33 | private unowned Gtk.Button btn_find_prev; 34 | [GtkChild] 35 | private unowned Gtk.Button btn_find_next; 36 | 37 | private MainWin win; 38 | private WebKit.WebView main_view; 39 | private WebKit.FindOptions _option = WebKit.FindOptions.NONE; 40 | 41 | internal bool search_prev {set;get;default=false;} 42 | 43 | internal SearchDialog(MainWin win) 44 | { 45 | //parent_win = win; 46 | this.win = win; 47 | main_view = win.view_current; 48 | } 49 | 50 | public WebKit.FindOptions option 51 | { 52 | get 53 | { 54 | _option = (option_case.get_active()?WebKit.FindOptions.CASE_INSENSITIVE:WebKit.FindOptions.NONE)| 55 | (option_wrap.get_active()?WebKit.FindOptions.WRAP_AROUND:WebKit.FindOptions.NONE); 56 | 57 | return _option; 58 | } 59 | } 60 | 61 | [GtkCallback] 62 | private void on_find_next_clicked (Gtk.Button self) 63 | { 64 | WebKit.FindController find_control; 65 | string find_text; 66 | find_text = entry_find.get_text(); 67 | 68 | main_view = this.win.view_current; /* update currnt view */ 69 | find_control = main_view.get_find_controller(); 70 | 71 | //print(find_text); 72 | 73 | if (find_text!="") 74 | { 75 | find_control.search(find_text,this.option,1024); 76 | } 77 | 78 | this.search_prev = false; 79 | } 80 | 81 | [GtkCallback] 82 | private void on_find_prev_clicked (Gtk.Button self) 83 | { 84 | WebKit.FindController find_control; 85 | string find_text; 86 | find_text = entry_find.get_text(); 87 | 88 | main_view = this.win.view_current; /* update currnt view */ 89 | find_control = main_view.get_find_controller(); 90 | //print(find_text); 91 | 92 | if (find_text!="") 93 | { 94 | find_control.search(find_text,this.option|WebKit.FindOptions.BACKWARDS,1024); 95 | } 96 | 97 | this.search_prev = true; 98 | } 99 | 100 | [GtkCallback] 101 | private void on_clear_clicked (Gtk.Button self) 102 | { 103 | WebKit.FindController find_control; 104 | 105 | find_control = main_view.get_find_controller(); 106 | 107 | find_control.search_finish(); 108 | } 109 | 110 | [GtkCallback] 111 | private bool key_enter_pressed (Gtk.Widget self,Gdk.Event evnt) 112 | { 113 | Gdk.EventKey key_evnt; 114 | uint keyval; 115 | 116 | key_evnt = evnt.key; 117 | keyval = key_evnt.keyval; 118 | 119 | if (keyval == Gdk.Key.Return) 120 | { 121 | if (this.search_prev) 122 | { 123 | this.btn_find_prev.clicked(); 124 | } 125 | else 126 | { 127 | this.btn_find_next.clicked(); 128 | } 129 | } 130 | 131 | return false; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/SectionList.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 XX Wu 3 | * 4 | * This file is part of Man Helper 5 | * 6 | * Man Helper is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * Man Helper is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * You should have received a copy of the GNU General Public License 15 | * along with Man Helper. If not, see . 16 | * 17 | * Authored by: XX Wu 18 | */ 19 | 20 | namespace ManHelper 21 | { 22 | /* List sections of the man pages*/ 23 | [GtkTemplate (ui = "/ui/section_list.ui")] 24 | internal class SectionList: Gtk.Box 25 | { 26 | MainWin win; 27 | 28 | [GtkChild] 29 | internal unowned Gtk.ComboBox section_combo; 30 | 31 | internal SectionList(MainWin win) 32 | { 33 | Gtk.TreeIter section_list_iter; 34 | this.win = win; 35 | 36 | var section_list_store = new Gtk.ListStore(2, typeof(string), typeof(string)); 37 | section_list_store.append(out section_list_iter); 38 | section_list_store.set(section_list_iter,0," ",1,"All Sections",-1); 39 | section_list_store.append(out section_list_iter); 40 | section_list_store.set(section_list_iter,0,"1:",1,"User Commands",-1); 41 | section_list_store.append(out section_list_iter); 42 | section_list_store.set(section_list_iter,0,"2:",1,"System Calls",-1); 43 | section_list_store.append(out section_list_iter); 44 | section_list_store.set(section_list_iter,0,"3:",1,"Library Functions",-1); 45 | section_list_store.append(out section_list_iter); 46 | section_list_store.set(section_list_iter,0,"4:",1,"Special Files",-1); 47 | section_list_store.append(out section_list_iter); 48 | section_list_store.set(section_list_iter,0,"5:",1,"File Formats",-1); 49 | section_list_store.append(out section_list_iter); 50 | section_list_store.set(section_list_iter,0,"6:",1,"Games",-1); 51 | section_list_store.append(out section_list_iter); 52 | section_list_store.set(section_list_iter,0,"7:",1,"Miscellany",-1); 53 | section_list_store.append(out section_list_iter); 54 | section_list_store.set(section_list_iter,0,"8:",1,"Administration",-1); 55 | section_combo.set_model(section_list_store); 56 | 57 | var cellrender1 = new Gtk.CellRendererText(); 58 | section_combo.pack_start(cellrender1, true); 59 | section_combo.add_attribute(cellrender1, "text", 0); 60 | var cellrender2 = new Gtk.CellRendererText(); 61 | section_combo.pack_start(cellrender2, true); 62 | section_combo.add_attribute(cellrender2, "text", 1); 63 | 64 | section_combo.set_active(0); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/ThemeCSS.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 XX Wu 3 | * 4 | * This file is part of Man Helper 5 | * 6 | * Man Helper is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * Man Helper is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * You should have received a copy of the GNU General Public License 15 | * along with Man Helper. If not, see . 16 | * 17 | * Authored by: XX Wu 18 | */ 19 | 20 | namespace ManHelper 21 | { 22 | /* CSS of the text */ 23 | internal class ThemeCSS 24 | { 25 | public Gdk.RGBA title_rgba = {}; 26 | public Gdk.RGBA heading_rgba = {}; 27 | public Gdk.RGBA regular_rgba = {}; 28 | public Gdk.RGBA bold_rgba = {}; 29 | public Gdk.RGBA italic_rgba = {}; 30 | 31 | private ThemeDialog theme_dialog = null; 32 | internal PreferDialog prefer_dialog = null; 33 | 34 | public ThemeCSS() 35 | { 36 | var black = "rgb(0,0,0)"; 37 | 38 | title_rgba.parse(black); 39 | heading_rgba.parse(black); 40 | regular_rgba.parse(black); 41 | bold_rgba.parse(black); 42 | italic_rgba.parse(black); 43 | } 44 | 45 | public ThemeCSS.from_theme (ThemeDialog theme_dialog) 46 | { 47 | this.theme_dialog = theme_dialog; 48 | 49 | title_rgba = this.theme_dialog.btn_title.get_rgba(); 50 | heading_rgba = this.theme_dialog.btn_heading.get_rgba(); 51 | regular_rgba = this.theme_dialog.btn_regular.get_rgba(); 52 | bold_rgba = this.theme_dialog.btn_bold.get_rgba(); 53 | italic_rgba = this.theme_dialog.btn_italic.get_rgba(); 54 | } 55 | 56 | public void set_theme_CSS(WebKit.WebView view) 57 | { 58 | // need further work using Javascript 59 | //print("java script here!"); 60 | /*WebKit.WebView view = null; 61 | if (this.theme_dialog != null) 62 | { 63 | view = this.theme_dialog.view; 64 | } 65 | else if (this.prefer_dialog != null) 66 | { 67 | //print("prefer!\n"); 68 | view = this.prefer_dialog.view; 69 | } 70 | else 71 | { 72 | print ("return here\n"); 73 | return; 74 | }*/ 75 | 76 | if (view == null) 77 | { 78 | return; 79 | } 80 | 81 | var css_str = this.to_string(); 82 | var java_script = @"var style = document.createElement('style'); style.innerHTML = '$(css_str)';document.head.appendChild(style)"; 83 | //print ("CSS: %s\n",css_str); 84 | /* 85 | try 86 | { 87 | print(java_script); 88 | yield view.run_javascript(java_script); 89 | } 90 | catch (Error e) 91 | { 92 | message(e.message); 93 | 94 | print("fail\n"); 95 | }*/ 96 | 97 | // need further work here 98 | //view.run_javascript.begin(java_script); 99 | 100 | /* Add a 1 ms delay to make javascript work */ 101 | Timeout.add(1,()=>{view.run_javascript.begin(java_script);return Source.REMOVE;}); 102 | } 103 | 104 | // placeholder, need further work here 105 | /*public ThemeCSS copy() 106 | { 107 | return new ThemeCSS(); 108 | }*/ 109 | 110 | public string to_string () 111 | { 112 | // need further work 113 | StringBuilder str_builder; 114 | str_builder = new StringBuilder(); 115 | 116 | var title_cstr = title_rgba.to_string(); 117 | var heading_cstr = heading_rgba.to_string(); 118 | var regular_cstr = regular_rgba.to_string(); 119 | var bold_cstr = bold_rgba.to_string(); 120 | var italic_cstr = italic_rgba.to_string(); 121 | 122 | //str_builder.append(""""""+" "); 130 | //print("%s\n%s\n%s\n%s\n%s\n",title_cstr,heading_cstr,regular_cstr,bold_cstr,italic_cstr); 131 | 132 | return str_builder.str; 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /ui/about_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 41 | 42 | -------------------------------------------------------------------------------- /ui/bookmarks_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 108 | 109 | -------------------------------------------------------------------------------- /ui/icon_manhelper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akarin123/manhelper/cdce635416fdd324c1644c1c00ac80db261b3ff1/ui/icon_manhelper.png -------------------------------------------------------------------------------- /ui/keyword_list.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 45 | 46 | -------------------------------------------------------------------------------- /ui/manhelper.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | manhelper.ui 5 | multitab_pager.ui 6 | page_zoomer.ui 7 | search_dialog.ui 8 | bookmarks_dialog.ui 9 | keyword_list.ui 10 | about_dialog.ui 11 | prefer_dialog.ui 12 | section_list.ui 13 | theme_dialog.ui 14 | icon_manhelper.png 15 | 16 | 17 | -------------------------------------------------------------------------------- /ui/manhelper.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 7 | 400 8 | 100 9 | 10 10 | 50 11 | 12 | 13 | True 14 | False 15 | gtk-add 16 | 17 | 18 | True 19 | False 20 | gtk-go-back 21 | 22 | 23 | True 24 | False 25 | user-bookmarks 26 | 27 | 28 | True 29 | False 30 | gtk-go-forward 31 | 32 | 33 | True 34 | False 35 | gtk-home 36 | 37 | 38 | True 39 | False 40 | gtk-index 41 | 42 | 419 | 420 | -------------------------------------------------------------------------------- /ui/multitab_pager.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | False 8 | False 9 | window-close 10 | 11 | 75 | 76 | -------------------------------------------------------------------------------- /ui/page_zoomer.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | False 8 | gtk-zoom-out 9 | 10 | 11 | True 12 | False 13 | zoom-fit-best 14 | 15 | 16 | True 17 | False 18 | gtk-zoom-in 19 | 20 | 106 | 107 | -------------------------------------------------------------------------------- /ui/prefer_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 302 | 303 | 1 304 | 96 305 | 12 306 | 0.5 307 | 5 308 | 309 | 310 | 1 311 | 9 312 | 6 313 | 1 314 | 1 315 | 316 | 317 | -------------------------------------------------------------------------------- /ui/search_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | False 8 | gtk-clear 9 | 10 | 11 | True 12 | False 13 | gtk-go-forward 14 | 15 | 16 | True 17 | False 18 | gtk-go-back 19 | 20 | 195 | 196 | -------------------------------------------------------------------------------- /ui/section_list.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 32 | 33 | -------------------------------------------------------------------------------- /ui/theme_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 197 | 198 | --------------------------------------------------------------------------------