pax_global_header00006660000000000000000000000064136013646370014523gustar00rootroot0000000000000052 comment=7a193d7692c9c76a1a94f17c4d30b585f77d177c libhandy-0.0.13/000077500000000000000000000000001360136463700133765ustar00rootroot00000000000000libhandy-0.0.13/.dir-locals.el000066400000000000000000000002141360136463700160240ustar00rootroot00000000000000( (c-mode . ( (c-file-style . "linux") (indent-tabs-mode . nil) (c-basic-offset . 2) )) ) libhandy-0.0.13/.editorconfig000066400000000000000000000006651360136463700160620ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true [meson.build] indent_size = 2 tab_size = 2 indent_style = space [*.{c,h,h.in}] indent_size = 2 tab_size = 2 indent_style = space max_line_length = 80 [*.css] indent_size = 2 tab_size = 2 indent_style = space [*.xml] indent_size = 2 tab_size = 2 indent_style = space [*.json] indent_size = 2 tab_size = 2 indent_style = space [NEWS] max_line_length = 72 libhandy-0.0.13/.gitignore000066400000000000000000000000751360136463700153700ustar00rootroot00000000000000_build *.swp *~ \#*# .\#* build .buildconfig .flatpak-builderlibhandy-0.0.13/.gitlab-ci.yml000066400000000000000000000061101360136463700160300ustar00rootroot00000000000000stages: - build - docs - test - package - deploy before_script: - apt-get -y update - apt-get -y install build-essential libgtk-3-doc lcov - apt-get -y build-dep . .tags: &tags tags: - librem5 .build: &build_steps echo "BUILD_OPTS=${BUILD_OPTS}" && export LC_ALL=C.UTF-8 && meson ${BUILD_OPTS} . _build && ninja -C _build build-debian-gcc: image: debian:buster <<: *tags stage: build variables: BUILD_OPTS: -Dgtk_doc=true -Db_coverage=true --werror script: - *build_steps artifacts: when: always paths: - _build build-debian-gcc-static: image: debian:buster <<: *tags stage: build variables: BUILD_OPTS: -Dintrospection=disabled -Dstatic=true -Dgtk_doc=false --werror script: - *build_steps build-debian-clang: image: debian:buster before_script: - apt-get -y update - apt-get -y install build-essential clang-tools - apt-get -y build-dep . <<: *tags stage: build script: - export LC_ALL=C.UTF-8 - meson . _build # With meson 0.49 we can do # SCANBUILD="scan-build --status-bugs" ninja scan-build # https://github.com/mesonbuild/meson/commit/1e7aea65e68a43b0319a4a28908daddfec621548 - ninja -C _build scan-build artifacts: when: always paths: - _build/meson-logs/scanbuild build-fedora-gcc: image: fedora:29 <<: *tags stage: build variables: BUILD_OPTS: -Dgtk_doc=true --werror before_script: - dnf -y update - dnf -y install @development-tools redhat-rpm-config dnf-plugins-core - dnf -y builddep data/packaging/rpm/libhandy.spec script: - *build_steps build-gtkdoc: image: debian:buster <<: *tags stage: docs dependencies: - build-debian-gcc script: - ninja -C _build meson-libhandy-doc 2>&1 | tee _build/doc/buildlog - if grep -qs 'warning' _build/doc/buildlog; then exit 1; fi artifacts: when: always paths: - _build/doc/html unit-test: image: debian:buster <<: *tags stage: test dependencies: - build-debian-gcc script: - xvfb-run -s -noreset ninja -C _build test - ninja -C _build coverage coverage: '/^\s+lines\.+:\s+([\d.]+\%)\s+/' artifacts: when: always paths: - _build package-debian: image: debian:buster <<: *tags stage: package script: - dpkg-buildpackage -uc -us - cp ../*.deb . artifacts: paths: - "*.deb" publish-docs: stage: deploy dependencies: - build-gtkdoc environment: name: production url: https://developer.puri.sm/projects/libhandy/ before_script: - export DEBIAN_FRONTEND=noninteractive - apt-get -y update - apt-get -y install ssh rsync tags: - librem5 script: # Don't publish in forks - '[ "$CI_PROJECT_NAMESPACE" == "Librem5" ] || exit 0' - mkdir -p ~/.ssh/ - chmod 0700 ~/.ssh/ - echo "$SSH_HOST_KEY" > ~/.ssh/known_hosts - touch ~/.ssh/id_rsa - chmod 0600 ~/.ssh/id_rsa - echo "$SSH_KEY" >> ~/.ssh/id_rsa - rsync -av -i --stats -e "ssh -l $SSH_USER -i ~/.ssh/id_rsa -p $SSH_PORT" _build/doc/html/ "$RSYNC_TARGET" only: - master libhandy-0.0.13/.gitlab/000077500000000000000000000000001360136463700147165ustar00rootroot00000000000000libhandy-0.0.13/.gitlab/issue_templates/000077500000000000000000000000001360136463700201245ustar00rootroot00000000000000libhandy-0.0.13/.gitlab/issue_templates/bug.md000066400000000000000000000031051360136463700212220ustar00rootroot00000000000000# What problem did you encounter? ## In what part of libhandy did you experience the problem? Note that multiple boxes may be checked. - [ ] build system - [ ] documentation - [ ] example application - [ ] HdyArrows - [ ] HdyColumn - [ ] HdyDialer - [ ] HdyLeaflet - [ ] somewhere else (please elaborate) ## What is the actual behaviour? ## What is the expected behaviour? ## How to reproduce? Please provide steps to reproduce the issue. If it's a graphical issue please attach screenshot. # Which version did you encounter the bug in? - [ ] I compiled it myself. If you compiled libhandy from source please provide the git revision via e.g. by running ``git log -1 --pretty=oneline`` and pasting the output below. - [ ] I used the precompiled Debian package (e.g. by running a prebuilt image). Please determine which package you have installed and paste the package status (dpkg -s) ``` $ dpkg -l | grep libhandy ii gir1.2-handy-0.0:amd64 0.0.3~203.gbp18952a amd64 GObject introspection files for libhandy ii libhandy-0.0-0:amd64 0.0.3~203.gbp18952a amd64 Library with GTK widgets for mobile phones ii libhandy-0.0-dev:amd64 0.0.3~203.gbp18952a amd64 Development files for libhandy $ dpkg -s libhandy-0.0-0 ``` # What hardware are you running libhandy on? - [ ] amd64 qemu image - [ ] Librem5 devkit - [ ] other (please elaborate) # Releveant logfiles Please provide relevant logs with ``G_MESSAGES_DEBUG=all `` libhandy-0.0.13/AUTHORS000066400000000000000000000005171360136463700144510ustar00rootroot00000000000000Adrien Plazas Bob Ham Dorota Czaplejewicz Guido Günther Heather Ellsworth Julian Richen Julian Sparber Sebastien Lafargue Zander Brown libhandy-0.0.13/COPYING000066400000000000000000000636421360136463700144440ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! libhandy-0.0.13/HACKING.md000066400000000000000000000122651360136463700147720ustar00rootroot00000000000000Building ======== For build instructions see the README.md Pull requests ============= Before filing a pull request run the tests: ```sh ninja -C _build test ``` Use descriptive commit messages, see https://wiki.gnome.org/Git/CommitMessages and check https://wiki.openstack.org/wiki/GitCommitMessages for good examples. Coding Style ============ We mostly use kernel style but * Use spaces, never tabs * Use 2 spaces for indentation GTK style function argument indentation ---------------------------------------- Use GTK style function argument indentation. It's harder for renames but it's what GNOME upstream projects do. *Good*: ```c static gboolean button_clicked_cb (HdyDialerCycleButton *self, GdkEventButton *event) ``` *Bad*: ```c static gboolean button_clicked_cb (HdyDialerCycleButton *self, GdkEventButton *event) ``` Braces ------ Everything besides functions and structs have the opening curly brace on the same line. *Good*: ```c if (i < 0) { ... } ``` *Bad*: ```c if (i < 0) { ... } ``` Single line `if` or `else` statements don't need braces but if either `if` or `else` have braces both get them: *Good*: ```c if (i < 0) i++; else i--; ``` ```c if (i < 0) { i++; j++; } else { i--; } ``` ```c if (i < 0) { i++; } else { i--; j--; } ``` *Bad*: ```c if (i < 0) { i++; } else { i--; } ``` ```c if (i < 0) { i++; j++; } else i--; ``` ```c if (i < 0) i++; else { i--; j--; } ``` Function calls have a space between function name and invocation: *Good*: ```c visible_child_name = gtk_stack_get_visible_child_name (GTK_STACK (self->stack)); ``` *Bad*: ```c visible_child_name = gtk_stack_get_visible_child_name(GTK_STACK(self->stack)); ``` Header Inclusion Guards ----------------------- Guard header inclusion with `#pragma once` rather than the traditional `#ifndef`-`#define`-`#endif` trio. Internal headers (for consistency, whether they need to be installed or not) should contain the following guard to prevent users from directly including them: ```c #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif ``` Only after these should you include headers. Signals ------- Prefix signal enum names with *SIGNAL_*. *Good*: ```c enum { SIGNAL_CYCLE_START = 0, SIGNAL_CYCLE_END, SIGNAL_LAST_SIGNAL, }; ``` Also note that the last element ends with a comma to reduce diff noise when adding further signals. Properties ---------- Prefix property enum names with *PROP_*. *Good*: ```c enum { PROP_0 = 0, PROP_CYCLE_TIMEOUT, PROP_LAST_PROP, }; ``` Also note that the last element ends with a comma to reduce diff noise when adding further properties. Comment style ------------- In comments use full sentences with proper capitalization and punctuation. *Good*: ```c /* Make sure we don't overflow. */ ``` *Bad:* ```c /* overflow check */ ``` Callbacks --------- Signal callbacks have a *_cb* suffix. *Good*: ```c g_signal_connect(self, "clicked", G_CALLBACK (button_clicked_cb), NULL); ``` *Bad*: ```c g_signal_connect(self, "clicked", G_CALLBACK (handle_button_clicked), NULL); ``` Static functions ---------------- Static functions don't need the class prefix. E.g. with a type foo_bar: *Good*: ```c static gboolean button_clicked_cb (HdyDialerCycleButton *self, GdkEventButton *event) ``` *Bad*: ```c static gboolean foo_bar_button_clicked_cb (HdyDialerCycleButton *self, GdkEventButton *event) ``` Note however that virtual methods like *_{init,constructed,finalize,dispose}* do use the class prefix. These functions are usually never called directly but only assigned once in *_constructed* so the longer name is kind of acceptable. This also helps to distinguish virtual methods from regular private methods. Self argument ------------- The first argument is usually the object itself so call it *self*. E.g. for a non public function: *Good*: ```c static gboolean expire_cb (FooButton *self) { g_return_val_if_fail (BAR_IS_FOO_BUTTON (self), FALSE); ... return FALSE; } ``` And for a public function: *Good*: ```c gint foo_button_get_state (FooButton *self) { FooButtonPrivate *priv = bar_foo_get_instance_private(self); g_return_val_if_fail (BAR_IS_FOO_BUTTON (self), -1); return priv->state; } ``` User interface files -------------------- User interface files should end in *.ui*. If there are multiple ui files put them in a ui/ subdirectory below the sources (e.g. *src/ui/main-window.ui*). ### Properties Use minus signs instead of underscores in property names: *Good*: ```xml 12 ``` *Bad*: ```xml 12 ``` Automatic cleanup ----------------- It's recommended to use `g_auto()`, `g_autoptr()`, `g_autofree()` for automatic resource cleanup when possible. *Good*: ```c g_autoptr(GdkPixbuf) sigterm = pixbuf = gtk_icon_info_load_icon (info, NULL); ``` *Bad*: ```c GdkPixbuf *pixbuf = gtk_icon_info_load_icon (info, NULL); ... g_object_unref (pixbuf); ``` Using the above is fine since libhandy doesn't target any older glib versions or non GCC/Clang compilers at the moment. libhandy-0.0.13/README.md000066400000000000000000000057121360136463700146620ustar00rootroot00000000000000# Handy [![Pipeline status](https://source.puri.sm/Librem5/libhandy/badges/master/build.svg)](https://source.puri.sm/Librem5/libhandy/commits/master) [![Code coverage](https://source.puri.sm/Librem5/libhandy/badges/master/coverage.svg)](https://source.puri.sm/Librem5/libhandy/commits/master) The aim of the Handy library is to help with developing UI for mobile devices using GTK/GNOME. ## License libhandy is licensed under the LGPL-2.1+. ## Build dependencies To build libhandy you need to first install the build-deps defined by [the debian/control file](https://source.puri.sm/Librem5/libhandy/blob/master/debian/control#L6). If you are running a Debian based distribution, you can easily install all those the dependencies making use of the following command ```sh sudo apt-get build-dep . ``` ## Building We use the Meson (and thereby Ninja) build system for libhandy. The quickest way to get going is to do the following: ```sh meson . _build ninja -C _build ninja -C _build install ``` For build options see [meson_options.txt](./meson_options.txt). E.g. to enable documentation: ```sh meson . _build -Dgtk_doc=true ninja -C _build libhandy-doc ``` ## Usage There's a C example: ```sh _build/examples/example ``` and one in Python. When running from the built source tree it needs several environment variables so use \_build/run to set them: ```sh _build/run examples/example.py ``` ### Glade To be able to use Handy's widgets in the glade interface designer without installing the library use: ```sh _build/run glade ``` ## Documentation The documentation can be found online [here](https://developer.puri.sm/projects/libhandy/unstable). Further examples on how to use the widgets in your favorite programming language can be found here: ### C - [calls](https://source.puri.sm/Librem5/calls) (HdyDialer) - [gnome-bluetooth](https://gitlab.gnome.org/GNOME/gnome-bluetooth) (HdyColumn) - [GNOME Settings](https://gitlab.gnome.org/GNOME/gnome-control-center) (HdyActionRow, HdyColumn, HdyLeaflet, HdyTitleBar) - [GNOME Web](https://gitlab.gnome.org/GNOME/epiphany) (HdyActionRow, HdyColumn, HdyComboRow, HdySearchBar) - [phosh](https://source.puri.sm/Librem5/phosh) (HdyDialer) ### Python 3 - [Daty](https://gitlab.gnome.org/World/Daty) (HdyColumn, HdyLeaflet, HdySearchBar, HdyTitleBar) - [HydraPaper](https://gabmus.gitlab.io/HydraPaper/) (HdyActionRow, HdyHeaderBar, HdyPreferencesGroup, HdyPreferencesPage, HdyPreferencesRow, HdyPreferencesWindow, HdyViewSwitcher) - [PasswordSafe](https://gitlab.gnome.org/World/PasswordSafe) (HdyColumn) - [Unifydmin](https://gitlab.com/gabmus/unifydmin) (HdyColumn, HdyLeaflet, HdyTitleBar) ### Rust - [Fractal](https://gitlab.gnome.org/GNOME/fractal) (HdyColumn, HdyLeaflet) - [Podcasts](https://gitlab.gnome.org/World/podcasts) (HdyColumn) ### Vala - [GNOME Contacts](https://gitlab.gnome.org/GNOME/gnome-contacts) (HdyLeaflet, HdyHeaderGroup, HdyTitleBar) - [GNOME Games](https://gitlab.gnome.org/GNOME/gnome-games) (HdyColumn, HdyTitleBar) libhandy-0.0.13/data/000077500000000000000000000000001360136463700143075ustar00rootroot00000000000000libhandy-0.0.13/data/leak-suppress.txt000066400000000000000000000004211360136463700176430ustar00rootroot00000000000000# Use via environment variable LSAN_OPTIONS=suppressions=data/leak-suppress.txt # Ignore fontconfig reported leaks. It's caches cause false positives. leak:libfontconfig.so.1 # https://gitlab.gnome.org/GNOME/gtk/merge_requests/823 leak:gtk_header_bar_set_decoration_layout libhandy-0.0.13/data/packaging/000077500000000000000000000000001360136463700162335ustar00rootroot00000000000000libhandy-0.0.13/data/packaging/rpm/000077500000000000000000000000001360136463700170315ustar00rootroot00000000000000libhandy-0.0.13/data/packaging/rpm/libhandy.spec000066400000000000000000000025051360136463700215010ustar00rootroot00000000000000%global _vpath_srcdir %{name} Name: libhandy Version: 0.0.13 Release: 1%{?dist} Summary: A library full of GTK widgets for mobile phones License: LGPLv2+ Url: https://source.puri.sm/Librem5/libhandy Source0: https://source.puri.sm/Librem5/libhandy/archive/master.tar.gz BuildRequires: gcc BuildRequires: gobject-introspection BuildRequires: gtk-doc BuildRequires: meson >= 0.40.1 BuildRequires: pkgconfig(gio-2.0) BuildRequires: pkgconfig(gladeui-2.0) BuildRequires: pkgconfig(glib-2.0) BuildRequires: pkgconfig(gmodule-2.0) BuildRequires: pkgconfig(gtk+-3.0) BuildRequires: pkgconf-pkg-config BuildRequires: vala %description %{summary}. %package devel Summary: Development libraries, headers, and documentation for %{name} Requires: libhandy = %{version}-%{release} %description devel %{summary}. %prep %setup -c -q %build %meson -Dexamples=false -Dgtk_doc=true %meson_build %install %meson_install %files %{_libdir}/libhandy*.so.* %{_libdir}/girepository-1.0/Handy*.typelib %files devel %{_includedir}/libhandy* %{_libdir}/libhandy*.so %{_libdir}/pkgconfig/libhandy*.pc %{_datadir}/gir-1.0/Handy*.gir %{_datadir}/glade/catalogs/libhandy.xml %{_datadir}/vala/vapi/libhandy*.deps %{_datadir}/vala/vapi/libhandy*.vapi %{_datadir}/gtk-doc %changelog * Fri May 18 2018 Julian Richen - 0.0.0-1 - Update to 0.0.0-1 libhandy-0.0.13/debian/000077500000000000000000000000001360136463700146205ustar00rootroot00000000000000libhandy-0.0.13/debian/README.source000066400000000000000000000020761360136463700170040ustar00rootroot00000000000000This package is maintained with git-buildpackage(1). It follows DEP-14 for branch naming (e.g. using debian/sid for the current version in Debian unstable). It uses pristine-tar(1) to store enough information in git to generate bit identical tarballs when building the package without having downloaded an upstream tarball first. When working with patches it is recommended to use "gbp pq import" to import the patches, modify the source and then use "gbp pq export --commit" to commit the modifications. The changelog is generated using "gbp dch" so if you submit any changes don't bother to add changelog entries but rather provide a nice git commit message that can then end up in the changelog. It is recommended to build the package with pbuilder using: gbp buildpackage --git-pbuilder For information on how to set up a pbuilder environment see the git-pbuilder(1) manpage. In short: DIST=sid git-pbuilder create gbp clone cd gbp buildpackage --git-pbuilder -- Guido Günther , Wed, 2 Dec 2015 18:51:15 +0100 libhandy-0.0.13/debian/changelog000066400000000000000000001601721360136463700165010ustar00rootroot00000000000000libhandy (0.0.13) amber-phone; urgency=medium [ Alexander Mikhaylenko ] * paginator-box: Stop using gtk_widget_set_child_visible() This function is meant for widgets that don't need to be mapped along with parent widget, not for scrolled out widgets. Additionally, using it causes strange side effects with GtkOverlay window z-ordering. Stop using it and instead track visiblity manually. Also, clarify the code a bit. * leaflet: Correctly handle 0 duration for swipe snap-back * swipe-tracker: Don't animate when the distance is 0. Usually it makes sense to restrict the minimum animation duration. However, if the progress already matches the end progress, it just causes a delay, so skip it completely. [ Julian Sparber ] * Keypad: Do not show allow typing + when only_digits is true. The keypad shouldn't allow typing or show + when only_digits is set to true. Therefore this adds the correct behavior. [ Guido Günther ] * Release libhandy 0.0.13 -- Guido Günther Fri, 27 Dec 2019 12:22:18 +0100 libhandy (0.0.12) experimental; urgency=medium [ Zander Brown ] * build: Don't install glade catalogue when used as submodule [ Alexander Mikhaylenko ] * swipe-tracker: Grab widget during the gesture * swipe-tracker: Animate when canceled. There are some cases where not animating the canceled gesture looks awkward. For example, when tapping a paginator while it animates. * swipe-tracker: Don't add GDK_ALL_EVENTS_MASK. That was a debugging leftover. * header-group: Fix a leftover GtkSizeGroup mention * paginator: Delegate hdy_paginator_scroll_to() to scroll_to_full() This will help to avoid duplicating code in later commits. * paginator-box: Add hdy_paginator_box_get_nth_child() * doc: Add 0.0.12 index * Add HdySwipeable. A common interface that swipeable widgets should implement and that HdySwipeGroup and HdySwipeTracker will use. * paginator: Implement HdySwipeable * swipe-tracker: Port to HdySwipeable. Use a HdySwipeable instead of GtkWidget. Remove 'begin', 'update' and 'end' signals and instead call HdySwipeable methods. * Add HdySwipeGroup. An object that allows to synchronize swipe animations of multiple widgets. This can be used to sync widgets between headerbar and window content area. * tests: Add HdySwipeGroup test * glade: Support HdySwipeGroup. Do the same thing as for HdyHeaderGroup. * leaflet: Fix the folding sliding children padding. Sets the children padding of the folding sliding animation depending on the surface they'll be drawn on. This doesn't change a thing for the sliding animation, but this will avoid the children to be moved when snapshotting them, which is needed for the over and under animations — which will be added in the next commit — to work correctly. * leaflet: Only clip visible area during transitions. Adjust width and height of the clip rectangle to avoid drawing areas outside of the widget. * Introduce HdyShadowHelper. This will be used in the following commits to add shadows to HeyLeaflet transitions. * leaflet: Dim bottom children during transitions. Draw a dimming layer and a drop shadow over bottom child during 'over' and 'under' mode and child transitions. The dimming, shadow and border styles are defined in CSS. The current style is based on the similar animation in WebKit. * swipe-tracker: Reduce base distance for vertical swipes. Use 300px instead of 400px, otherwise it can be hard to use on small touchpads. * paginator-box: Adjust index when removing pages. Prevent jumping when removing pages to the left of the current one. * paginator: Support discrete scrolling. Support scrolling on devices like mice. Switch a page when a scroll event arrives and add a delay to prevent too fast scrolling. Use animation duration as a delay, but don't let it go below 250ms, mainly to ensure it still works with animations disabled. Fixes https://source.puri.sm/Librem5/libhandy/issues/155 * swipe-tracker: Stop handling trackpoint. Handle it like discrete scrolling instead. * leaflet: Mention replacements in deprecations. Have more useful warnings. * leaflet: Mark child-transition and mode-transition as deprecated. Properties are deprecated too, not just accessors. * leaflet: Ignore deprecations for transition type acccessor declarations. Since enums are deprecated now, these declarations trigger warnings in modules that use libhandy. Since these functions are already deprecated anyway, silence these warnings. * deprecation-macros: Stop referencing nonexistent macros. G_DEPRECATED_* and G_DEPRECATED_*_FOR aren't a thing. * swipe-tracker: Make dragging touch-only. Since HdyPaginator has mouse scrolling now, there's no need to have dragging available on non-touch devices, so drop it. * paginator-box: Wrap children into child info structs. This will allow to carry additional data for them later. * paginator-box: Put children into their own GdkWindows. This allows to stop doing size allocation on each frame, and will allow to implement drawing cache in the next commit. * paginator-box: Implement drawing cache. Keep a Cairo surface for each child. Paint children onto their surfaces, then compose the final image. Instead of painting the whole children, track invalidations and paint only changed parts. This means most paginator redraws don't involve any child redraws. This should significantly speed up scrolling when children are expensive to draw. * paginator-box: Add animation-stopped signal. This will be used in the next commit to add page-changed signal to HdyPaginator. * paginator: Add page-changed signal. Allows to know when the current page has changed, this can be used to implement "infinite scrolling" by connecting to this signal and amending the pages. * leaflet: Allocate last visible child during child transitions. Fixes one cause of https://source.puri.sm/Librem5/libhandy/issues/85 * keypad: Immediately assign g_autoptrs to NULL. Avoid compile-time warnings. * paginator-box: Create window with correct dimensions. It doesn't matter because it gets overridden later, but still fix it. * example: Remove leftover adjustments. See aa7a4eca68d8c75ff6347202c90515c5aea30c64 * paginator-box: Fix hdy_paginator_box_get_nth_child() Return the actual widget, not child info struct. A leftover from 710bcaacb97bdfac6061726a77665235279d4fe6 * leaflet: Use provided duration for child transitions. Actually use the value from the function argument. * swipeable: Provide swipe direction when preparing. This will allow to restrict the swipe to only one direction for leaflet. * swipeable: Distinguish direct and indirect swipes. Add "direct" parameter to hdy_paginator_begin_swipe() and the corresponding vfunc, providing a way to tell apart swipes started via HdySwipeGroup sync. This will be used to have leaflet in headerbar that's not swipeable, but can still animate along with leaflet in content area. * swipe-tracker: Skip swipes in wrong direction. Prevent swiping if the direction doesn't match tracker orientation. This allows to have GtkScrolledArea inside or around swipeable widgets without swipes taking over scrolling. * leaflet: Add allow-visible child property. This will be used to prevent swiping to widgets such as separators. * leaflet: Add properties for controlling swipes. This will allow to selectively enable back and/or forward swipes for HdyLeaflet. By default swipes are disabled. * leaflet: Implement back/forward swipe gesture. Implement HdySwipeable and use HdySwipeTracker to detect back/forward swipes. Use can-swipe-back and can-swipe-forward properties for controlling swipes, and use allow-visible child property to exclude certain widgets, such as separators, from the gesture. Multiple leaflets can be synced via HdySwipeGroup. * example: Enable back swipe in the leaflet. Set can-swipe-back=true on the content leaflet, allow-visible=false for separators and use HdySwipeGroup for syncing leaflets rather than binding visible child name. * leaflet: Queue relayout after child transition ends. Prevents close button from occasionally disappearing after swipes. * swipe-tracker: Add 'allow-mouse-drag' property * paginator: Add 'allow-mouse-drag' property. Usually we don't want this, because there's scrolling. However, phosh still needs this for lockscreen, hence optionally allow it. * paginator-box: Register window before setting parent. Prevents newly created widgets from reusing parent's window. Fixes a regression from e6a477492de6cc4d5107147b9724980ffd7343ea Fixes https://source.puri.sm/Librem5/libhandy/issues/165 * swipeable: Fix signal names for docs * swipe-group: Don't escape tag names for docs * leaflet: Deprecate old transition type properties. They did already have the deprecated flag, but weren't shown as deprecated in docs. * Update @See_also for swipeable widgets. Mention HdyLeaflet in HdySwipeable, HdySwipeGroup and HdySwipeTracker. [ louib ] * Fix typo in README. * Remove casts requiring increased alignment. Some casts were increasing the required alignment in callbacks, raising warnings when compiled on arm with gcc. [ Guido Günther ] * Add deprecation macros. The macros are libhandy internal (should not be used in application code) and are as such marked with a '_'. This also makes gtk-doc happy since it treats it as a public symbol otherwise. * Deprecate all hdy-dialer{-cycle}-button api. It's considered HdyDialer internal API * HdyDialer: Remove excessive '*' * build: Install new header file. Fixes: ac94e649aac540c1ecaa9df98364049e182605cc * Release libhandy 0.0.12 [ Adrien Plazas ] * leaflet: Clip children when drawing unfolded. This will clip children to ensure they don't get drawn on or under the visible child, which will allow to create mode transition animations where other children appear to be drawn under the visible child. * leaflet: Clip the end surface when drawing folded. This will clip the end surface to ensure it doesn't get drawn on or under the visible child, which will allow to create mode transition animations where other children appear to be drawn under the visible child. * leaflet: Add the over and under mode transition animations. This allows the mode transition animation to match the semantic of the over and under child transitions. * leaflet: Unify the transition types. Add the HdyLeafletTransitionType enumeration and the transition-type property to define both the mode and child transitions, as having them different makes no sense and could lead to spatialization issues. This new type doesn't offer a crossfade transition on purpose as it was deemed inappropriate for the leaflet, for which the position of the children is inherently important. This also deprecates the two previous properties and their respective types. Fixes https://source.puri.sm/Librem5/libhandy/issues/92. * leaflet: Remove the over and under mode transitions. There is no point in adding enum values and deprecating them in the same version, so let's just remove them. The animations are still available via the newly added HdyLeafletTransitionType type and the transition-type property, so this also encourages migrating to the new API. * examples: Add a Leaflet page. This adds a page to demo the leaflet transitions, drops usage of the deprecated leaflet transition types and properties, and defaults to the 'over' transition to demo it and its shadow effect. * Deprecate HdyArrows. As far as we know, nothing uses it anymore and it's not part of our latest designs. Fixes https://source.puri.sm/Librem5/libhandy/issues/126. * examples: Drop the Arrows page. HdyArrows is now deprecated, so we don't want to promote it. * leaflet: Drop some old TODOs. We just don't need them anymore. * leaflet: Add Alexander Mikhaylenko's copyright. His work on this class is far from negligeable, let's reflect that in the copyright. * view-switcher-button: Fix the action bar hover style. This makes the buttons out of a header bar slightly lighter when hovered and the window is focused. Previously they were the same color as the unfocused buttons and the action bar, making them look less good and harder to use. Fixes https://source.puri.sm/Librem5/libhandy/issues/147. [ Julian Sparber ] * Keypad: Add a general keypad. This is based on HdyDialer, but with more flexible API. The new Keypad allows to set a custom Widget to the left/right lower corner, replacing the original widget. The Keypad extents directly GtkGrid which exposes all grid properties. It also allows to replace/change every button in the Keypad, just like in GtkGrid. It also adds a GtkEntry which can be used as the focus widget, it has the key-press-event already connected and it grabs focus once it's mapped. The Entry isn't part of the keypad, it's just a convenienced way to create a Entry, you would expect to use with a keypad. * Tests: add keypad tests * Docs: add docs and demo for keypad * Dialer: deprecate hdydialer * HdyDialer: Remove it from the demo. Remove the dialer from the demo since it's deprecated. * HdyDialer: Deprecate objects related to dialer. HdyDialerButton, HdyDialerCycleButton and HdyDialer objects where not deprecated, only there methods were. [ Oliver Galvin ] * README: minor punctuation fixes, and update Fractal URL to GNOME namespace * docs: Consistently use full sentences in short descriptions. * docs: Add sections about building and bundling to the 'Compiling with libhandy' page, and generally tidy the page. * docs: Update copyright year range. * meson: fix configure-time warning - Use the 'pie' kwarg instead of passing '-fpie' manually. Also bump Meson to 0.49.0, when the pie kwarg was added. * meson: Tidy build files. Use / operator (added in Meson 0.49.0) instead of join_paths. Use package_api_name variable to avoid repetition. * style: Remove odd tabs as per 'Coding Style' in HACKING.md, and fix typo. [ Ting-Wei Lan ] * keypad: Fix compilation error for clang. Function hdy_keypad_button_get_digit is declared to return 'char' in src/hdy-keypad-button-private.h but defined to return 'const char' in src/hdy-keypad-button.c. This is not allowed by clang. Since it is unusual to mark a return value itself as const, just drop const here. -- Guido Günther Thu, 12 Dec 2019 09:49:04 +0100 libhandy (0.0.11) experimental; urgency=medium [ Adrien Plazas ] * dialer: Work around GtkGrid row homogeneity. Puts the buttons into a vertical size group rather than making the rows homogeneous. This prevents a bug from GtkGrid to make the buttons too tall when the action buttons are hidden. * dialog: Don't warn if the titlebar isn't a GtkHeaderBar. Using another widget is perfectly valid, so we should just return instead. * dialog: Refactor the transient-for workaround. This will make introducing new properties simpler. * dialog: Add the narrow property. * header-bar: Show a back button in a narrow HdyDialog. If a header bar is in a narrow HdyDialog, it will display a back button at its start in place of its usual window decorations. * examples: Add a complex HdyDialog example. This shows how to use HdyHeaderBar and HdyDialog to create a more complex adaptive dialog. * header-bar: Show a back button on small non-sovereign windows. This will show the back button not only in small HdyDialog but in all small windows that are not sovereign. * meson: Set the log domain. This makes the log messages from libhandy look like `Handy-Debug: …` rather than `** Debug: …`, making them easier to distinguish. * README.md: Update the documentation URL. It's on the developer.puri.sm now. * Add animation helpers. Add various animation helpers to avoid coyping them around. * squeezer: Support animation disablement. This will animate the child transitions only if animations are enabled. * preferences-group: Use the h4 style class. Use the h4 style class instead of hardcoding the bold style for the preferences group title, and implement a fallback making the font bold. This is needed by elementary to use their own style. * animation: Make some functions public. This makes hdy_get_enable_animations() and hdy_ease_out_cubic() public. * view-switcher-button: Don't make transparent on hover. This doesn't make the background transparent when hovering and apply the same style as non-hovered buttons on hovered buttons in a headerbar. [ Gabriele Musco ] * Added Unifydmin to Python 3 examples * Add HydraPaper to Python 3 examples [ Ting-Wei Lan ] * Don't require GNU sed [ Jeremy Bicha ] * Debian packaging improvements [ Guido Günther ] * debian: Ship example program and files * Release libhandy 0.0.11 [ Alexander Mikhaylenko ] * search-bar: Hide start and end boxes instead of close button. * glade: Update catalog dtd. * Add new HdySwipeTracker widget. This will be used to implement swipes in new widgets. * Add new HdyPaginator widget. Display set of pages with swipe based navigation. [ David Boddie ] * Deploy documentation for the master branch [ Michael Catanzaro ] * glade: Don't install glade files outside build prefix. -- Guido Günther Tue, 27 Aug 2019 12:50:01 +0200 libhandy (0.0.10) experimental; urgency=medium [ Adrien Plazas ] * .editorconfig: Add CSS * arrows: Refresh HdyArrowsDirection docs. This moves the HdyArrowsDirection documentation to the C file and removes the final period from the values definitions, like for all other enums documentations. * docs: Add section for new symbols in 0.0.10 * view-switcher: Fix stack children callbacks. This fixes the callbacks when a child is added or removed from the view switcher's stack. * view-switcher-button: Make an active button's label bold. This makes the view switcher easier to read. It uses multiple labels with or without the specific style rather than a single label with the style toggled on and off to ensure the size requests don't change depending on whether the button is active or not. * leaflet: Synchronize paired notifications. This ensures users can't react to a visible child change notification or a fold change notification before we finish emitting all related notifications. * Add HdySqueezer. This can be used to automatically hide a widget like a HdyViewSwitcher in a header bar when there is not enough space for it and show a title label instead. Fixes https://source.puri.sm/Librem5/libhandy/issues/100 * examples: Use a HdySqueezer. Use a HdySqueezer in the view switcher window to show either the view switcher in the header bar, or a window title and a view switcher bar depending on the window's width. * view-switcher-button: Allow to elipsize in narrow mode. This will be used to let HdyViewSwitcherBar reach even narrower widths. * view-switcher: Allow to elipsize in narrow mode. This will be used to let HdyViewSwitcherBar reach even narrower widths. * view-switcher-bar: Ellipsize in narrow mode. This lets HdyViewSwitcherBar reach even narrower widths. * view-switcher-button: Use buttons borders in size. When computing the size of the button, take the button's border into account. Fixes https://source.puri.sm/Librem5/libhandy/issues/108 * view-switcher-bar: Sort properties by alphabetical order. This fixes a code style error and will avoid to propagate it as the file gets edited. * view-switcher-bar: Add margins. Add margings around the view switcher to better match the mockups. * view-switcher: Define a minimum natural width. This prevents the buttons from looking terribly narrow in a wide bar by making them request a minimum good looking natural size. * Add HdyPreferencesRow. This will be used as the base row for the preferences window, offering data needed to search a preference entry. * action-row: Extend HdyPreferencesRow. This allows to use HdyActionRow and its derivatives as preferences rows. * Add HdyPreferencesGroup. This will be used to group preferences rows as a coherent group in a preferences page. * Add HdyPreferencesPage. This will be used to group preferences as pages in a preferences window. * Add HdyPreferencesWindow. This allows to easily create searchable preferences windows. Fixes https://source.puri.sm/Librem5/libhandy/issues/101 * examples: Add a HdyPreferencesWindow example * Add private GtkWindow functions. Add the private GtkWindow functions _gtk_window_toggle_maximized() and gtk_window_get_icon_for_size() which will be used in the next commit by HdyHeaderBar. * Add HdyHeaderBar. Fork GtkHeaderBar to help fixing shortcomings caused by adaptive designs or coming from GtkHeaderBar itself as features are not accepted into GTK 3 anymore. Fixes https://source.puri.sm/Librem5/libhandy/issues/102 * examples: Use HdyHeaderBar in the View Switcher page. This correctly centers the view switcher and demoes HdyHeaderBar. * view-switcher: Recommend to use a HdyHeaderBar. This will help users of HdyViewSwitcher to know how to make it look good in a header bar. * examples: Drop un unused signal connection. This avoids a run time warning. * docs: Add images for HdyViewSwitcher and HdyViewSwitcherBar * preferences-window: Strictly center the header bar. This makes the header bar's widgets look better by ensuring they are always centered, even if it means they will be narrower. * conbo-row: Make the popover relative to the arrow. Consistently point to the arrow rather than sometimes to the arrow and sometimes to the invisible box containing the current value. * combo-row: Add HdyComboRowGetName. Replace HdyComboRowCreateLabelData by HdyComboRowGetName and keep a reference to in the combo row to allow accessing it externally. It will be needed to automatically handle converting the value into a name to display as the subtitle of the row. * combo-row: Add the use-subtitle property. Allow to display the current value as the subtitle rather than at the end of the row. Fixes https://source.puri.sm/Librem5/libhandy/issues/95 * header-bar: Render margins and borders. Fixes https://source.puri.sm/Librem5/libhandy/issues/121 [ Zander Brown ] * Add HdyViewSwitcherButton. This will be used in the next commit by HdyViewSwitcher. * Add HdyViewSwitcher. This more modern and adaptive take on GtkStackSwitcher helps building adaptive UIs for phones. Fixes https://source.puri.sm/Librem5/libhandy/issues/64 * Add HdyViewSwitcherBar. This action bar offers a HdyViewSwitcher and is designed to be put at the bottom of windows. It is designed to be revealed when the header bar doesn't have enough room to fit a HdyViewSwitcher, helping the windows to reach narrower widths. * examples: Add the View Switcher page. This example presents a HdyViewSwitcher and a HdyViewSwitcherBar in their own window. Currently both are visible at the same time, a later commit should make only one visible at a time, depending on the available width. [ Aearil ] * Update components list for the external projects in the README [ Mohammed Sadiq ] * dialog: Fix typos in documentation * demo-window: Fix typo in property name [ Oliver Galvin ] * Change GTK+ to GTK * Fix a few typos and grammatical mistakes * Expand the visual overview. Add more widgets and a comparison of HdyDialog [ Guido Günther ] * Release libhandy 0.0.10 * HACKING: - Properly end emphasis - Document extra space after function calls * ci improvements - Split doc build to different stage - Split out unit tests to different stage - Drop coverage on Fedora. It's not evaulated anyway - Split out build commands - Drop tests from static build - Move Debian package to packaging stage * gitlab-ci: Archive the build debs * HdyArrows: - Fix obvious documentation errors - Only redraw widget if visible - Don't emit notify signals on unchanged properties - Redraw arrows on property changes * HdyDemoWindow: Don't schedule arrow redraws * Add suppression for ASAN * tests-dialer: cleanups * HdyDialer: Make show_action_buttons match the initial property default -- Guido Günther Wed, 12 Jun 2019 17:23:21 +0200 libhandy (0.0.9) experimental; urgency=medium [ Benjamin Berg ] * glade: Mark ActionRow properties as translatable/icon. Without this, it is impossible to set the translatable flag in glade, making it hard to create proper UI definitions. [ Bastien Nocera ] * Use correct i18n include. From the Internationalization section of the GLib API docs: In order to use these macros in an application, you must include . For use in a library, you must include after defining the GETTEXT_PACKAGE macro suitably for your library * Fix broken translations in all libhandy applications. Translations in all the applications using libhandy would be broken after a call to hdy_init() as it changed the default gettext translation domain. See https://gitlab.gnome.org/GNOME/gnome-control-center/issues/393 [ Adrien Plazas ] * examples: Update the Flatpak command. The command should changed with the demo application name. * leaflet: Improve the slide child transition description. This makes the slide child transition description match the one of the slide mode transition one. * action-row: Upcast self to check the activated row. Upcast the HdyActionRow rather than downcasting the activated row to compare their pointers. This prevents error messages when a sibbling row that isn't a HdyActionRow is activated. Also use a simple cast rather than a safe cast as it is there only to please the compiler and is useless for a pointer comparison and it's faster. * Drop 'dialer' from the UI resources path. This makes the UI file paths more correct and simpler. * leaflet: Add hdy_leaflet_stop_child_transition() This makes the code clearer by encapsulating child mode transition cancellation into its own function. * leaflet: Factorize bin window move and resize. This ensures we move or resize it consistently. * leaflet: Move the bin window on child transition cancellation. This avoids the children to be drawn out of place when a mode transition is triggered while a child transition was ongoing. Fixes https://source.puri.sm/Librem5/libhandy/issues/93 * Add HDY_STYLE_PROVIDER_PRIORITY. Add and use HDY_STYLE_PROVIDER_PRIORITY to help ensuring custom styling is applied consistently and correctly accross all the library. * expander-row: Move the custom style to a resource. This makes the code cleaner, easier to read, and simnpler to modify. * combo-row: Move the custom style to a resource. This makes the code cleaner, easier to read, and simnpler to modify. * expander-row: Add the expanded property. This can be used to reveal external widgets depending on the state of the row. [ Guido Günther ] * debian: Test GObject introspection. This makes sure we have the typelib file installed correctly. * debian/tests: Drop API version from include. This makes sure we respect pkg-config's findings. * examples: Add API version to demo name. This makes different versions co-installable. * build: Don't hardcode API version * Release libhandy 0.0.9 -- Guido Günther Thu, 07 Mar 2019 12:37:34 +0100 libhandy (0.0.8) experimental; urgency=medium [ Adrien Plazas ] * examples: Use the "frame" stylesheet on listboxes. This avoids using GtkFrame where it's not relevant and shows the example. * examples: Refactor the Dialer panel. This makes it more in line with the other panels. * examples: Refactor the Arrows panel. This makes it more in line with the other panels. * examples: Fix the Lists panel column width. We were accidentally using the widths from the Column panel. * examples: Fix a typo * action-row: Add the row-header style class to the header box. This will allow to style the row's header separately. * expander-row: Add the expander style class. This will allow to style the row's padding appropriately to be used as an expander. * README.md: Add GNOME Settings and GNOME Web to users * meson: Don't install if it's a static subproject * title-bar: Drop useless definitions and inclusions. These were copy and paste errors. * README.md: Add gnome-bluetooth as a user * examples: Rename the example program to handy-demo. This also renames the type and files to match the new name. Fixes https://source.puri.sm/Librem5/libhandy/issues/81 * meson: Fix the examples option description. Fixes https://source.puri.sm/Librem5/libhandy/issues/82 * expander-row: Animate the arrow rotation. Because we can! * leaflet: Support RTL languages when unfolded. Fixes https://source.puri.sm/Librem5/libhandy/issues/86 [ Benjamin Berg ] * Add -s -noreset to xvfb-run calls. Xvfb will close when the last client exists, which may be the cause of sporadic test failures. Add -s -noreset to the command line to prevent this from happening. * combo-row: Fix memory leak g_list_model_get_item returns a referenced GObject which needs to be unref'ed. * combo-row: Fix memory leak in set_for_enum * value-object: Add an object to stuff a GValue into a GListModel. This is useful to store arbitrary (but simple) values inside a HdyComboRow. * example: Use value object rather. The code was storing strings in labels, just to extract them again. Also, the code was leaking the labels as g_list_store_insert does not sink the reference of the passed object. * tests: Add tests for HdyValueObject * action-row: Destroy the contained widget. The GtkBox that contains everything is an internal child which must be destroyed explicitly. [ Guido Günther ] * run.in: Set GLADE_MODULE_SEARCH_PATH as well. This makes sure we're using the freshly built module when running from the source tree. * Release libhandy 0.0.8 [ Pellegrino Prevete ] * README: added Daty to example apps * build: Force default libdir location for libhandy target on Windows to keep MinGW compatibility [ Alexander Mikhaylenko ] * leaflet: Add missing check for moving child window. Prevent child window from moving in transitions that don't require it, instead just resize it. Fixes https://source.puri.sm/Librem5/libhandy/issues/80 * leaflet: Drop commented out 'under' child transition. It's going to be replaced with the actual implementation in the next commit. * leaflet: Make 'over' child transition symmetric. Implement 'under' child transition animation, use it for 'over' for right and down directions, matching 'over' description. Fixes https://source.puri.sm/Librem5/libhandy/issues/79 * leaflet: Add 'under' child transition. Use same animations as 'over', but with reversed directions. Documentation descriptions by Adrien Plazas. Fixes https://source.puri.sm/Librem5/libhandy/issues/84 * leaflet: Clip bottom child during child transitions. Prevents bottom child from being visible through the top one during 'over' and 'under' child transitions. [ maxice8 ] * meson: pass -DHANDY_COMPILATION to GIR compiler. Fixes cross compilation of GIR in Void Linux. -- Guido Günther Fri, 15 Feb 2019 11:27:35 +0100 libhandy (0.0.7) experimental; urgency=medium [ Adrien Plazas ] * glade: Add row widgets to the widget classes. They are missing and don't appear in Glade. * glade: Add that HdySearchBar. It's in libhandy since 0.0.6 * action-row: Handle show_all() This avoids an empty image, an empty subtitle and an empty prefixes box to be visible when calling show_all(), as they are handled by the row itself. * action-row: Add the Since annotation to properties * example: Make the row with no action non-activatable * tests: Init libhandy. This ensures we run the test the same way applications are expected to run libhandy. * docs: Add section for new symbols in 0.0.7 * action-row: Add the activatable-widget property. This allows to bind the activation of the row by a click or a mnemonic to the activation of a widget. * action-row: Chain up the parent dispose method * combo-row: Release the model on dispose. This avoids errors when trying to disconnect signals on finalization. * combo-box: Rename selected_position to selecxted_index. This will better match the name for its accessors which will be added in the next commit. * combo-row: Add the selected-index property. This allows to access the selected item. * main: Explicitely load the resources in hdy_init() This is mandatory to use resources of a static version of libhandy, and is hence mandatory to allow to build libhandy as a static library. * meson: Bump Meson to 0.47.0. This is required to use the feature option type in the next commit. * meson: Make introspection and the Glade catalog features. This avoids having to disable them when their dependencies aren't available and it will allow to disable them properly when libhandy will be allowed to be built as a static library in the next commit. * meson: Allow to build as a static library. This also disables the Glade catalog as it doesn't work with a static libhandy. * action-row: Drop pointers to internals on destruction. This avoids crashes when trying to access pointers to already dropped widgets. Fixes https://source.puri.sm/Librem5/libhandy/issues/69 * expander-row: Drop pointers to internals on destruction. This avoids crashes when trying to access pointers to already dropped widgets. Fixes https://source.puri.sm/Librem5/libhandy/issues/69 * examples: Make the Dialog section look nicer. This improves the spacing, adds and icon and adds a description to the Dialog section. * dialog: Close when pressing the back button. Close the dialog instead of destroying it when clicking the back button. This is the same behavior as when pressing escape or clicking the close button and allows the dialog to be reused as some applications like to do. Fixes https://source.puri.sm/Librem5/libhandy/issues/70 [ louib ] * Add GNOME Contacts as example [ Guido Günther ] * HdyComboRow: Don't use g_autoptr for GEnumClass g_autoptr for GEnumClass was added post 2.56, so using it makes it harder for people to package for distros. Not using g_autoptr there doesn't make the code much less readable. * HdyDialer: Don't use class method slot for 'delete' We used the one of 'submit' so far due to a c'n'p error. (Closes: #67) * HdyComboRow: hdy_combo_row_get_model: Add missing scope annotation * gitlab-ci: Build static library. The library build is sufficiently different that we want to run the build and tests. * Release libhandy 0.0.7 [ David Cordero ] * Update documentation regarding build dependencies [ Zander Brown ] * Implement HdyDialog, an adaptive GtkDialog https://source.puri.sm/Librem5/libhandy/issues/52 * example: Add to example application. Silly simple demo of HdyDialog. [ Benjamin Berg ] * combo-row: Rework selected-index property setting and notification. The notify::selected-index signal was not selected in most cases. Rework the selection handling to ensure that it is always emited when it changes or if the module is replaced. Also fixed are a few checks on whether the selection index is valid. -- Guido Günther Fri, 18 Jan 2019 14:38:30 +0100 libhandy (0.0.6) experimental; urgency=medium [ Adrien Plazas ] * Set relevant ATK roles. This will help the widgets to be more accessible. * doc: Rephrase the unstability ack section. Rephrase the documentation explaining how to include libhandy in a way that could include other languages such as Vala. * doc: Document the unstability ack for Vala * Guard header inclusions with #pragma once. This standardizes the header inclusion guards to #pragma once, which shouldn't be a problem as we already were using it in some files. * hacking: Document header inclusion guard preferences * example: Disable more libhandy options in Flatpak. Disable generation of the GObject Introspection files, the VAPI and the tests in the example Flatpak as they are not used by it. * arrow: Use a measure() method. This will simplify porting to GTK+ 4. * column: Use a measure() method. This will simplify porting to GTK+ 4. * dialer-button: Use a measure() method. This will simplify porting to GTK+ 4. * leaflet: Use a measure() method. This will simplify porting to GTK+ 4. * init: Make the arguments optional. Annotate the arguments of hdy_init() with (optional) to specify that NULL arguments are valid. This also replaces the deprecated (allow-none) by (nullable) to specify that the array pointed at by argv can be NULL. * init: Document that libhandy can't be reinitialized * Normalize and document private header guards * Add HdySearchBar. This is similar to GtkSearchBar except it allows the central widget (typically a GtkEntry) to fill all the available space. This is needed to manage an entry's width via a HdyColumn, letting the entry (and by extention the search bar) have a limited maximum width while allowing it to shrink to accomodate smaller windows. * example: Add the 'Search bar' page. This adds a demo of HdySearchBar. * example: Put the content in a scrolled window. This ensures the example can fit windows of any height. This also makes the stack containing the content non vertically homogeneous so the scrollbar appears only on examples needing it, while keeping it horizontally homogeneous for to keep when the leaflets will be folded consistent. * build: Set the shared object install directory. This is required for Meson subprojects to work as intended. * build: Do not install hdy-public-types.c. There is no point in installing this generated C file. * leaflet: Allow editing the children list when looping through it. This avoids potential crashes when destroying a leaflet and this avoids leaks as not all children where looped through as the children list was edited while being looped through when destroying the leaflet. This fixes https://source.puri.sm/Librem5/libhandy/issues/42. * Add hdy_list_box_separator_header() This list box header update function is commonly used by many applications and is going to be used by HdyComboRow which is going to be added to libhandy later.This makes it available for everyone. * examples: Use hdy_list_box_separator_header() This makes the code simpler. * Add HdyActionRow. This implements a very commonly used list box row pattern and make it convenient to use. It is going to be used as the base class for many other commonly used row types. * examples: Use HdyRow. This makes the code simpler and demoes the widget. * Add HdyExpanderRow * Add HdyEnumValueObject. This will be used in the next commit to use enumeration values in a GListModel. * Add HdyComboRow * examples: Add the Lists page. This page presents GtkListBox related widgets like HdyRow and its descendants. * examples: Put the scrolled window in the end pane size group. This fixes the fold synchronization of the leaflets in the example application's window. [ Guido Günther ] * hdy-enums: Make build reproducible. Use @basename@ instead of @filename@ since the later uses the full path which varies across builds. * HACKING: Clarify braces in if-else. Document common practice in the other files. * spec: Sort dependencies * spec: Build-depend on libgladeui-2.0 * gitlab-ci: Deduplicate tags * gitlab-ci: Build on Fedora as well. This gives us more confidence that we build succesfully and without warnings on an OS much used by GNOME developers. It also makes sure we validate the spec file. * gitlab-ci: Switch to clang-tools clang-3.9 does not contain scan-build anymore. * HdyHeaderGroup: Cleanup references to header bars in dispose. The dispose heandler is meant to break refs to other objects, not finalize. * HdyHeaderGroup: Disconnect from header bar's signals during dispose. The header bars might still emit signals which leads to CRITICALS or actual crashes. Fixes parts of #56 * docs: Add section for new symbols in 0.0.6 * Annotate APIs new in 0.0.6 * Release libhandy 0.0.6 [ Alexander Mikhaylenko ] * init: Add (transfer none) to argv parameter. This allows to call the function from Vala more easily. * header-group: Ref itself instead of header bars. When adding a header bar, ref the header group and connect to 'destroy' signal of the header bar. When a header bar is destroyed or hdy_header_group_remove_header_bar() is called, unref the header bar and remove it from the list. This way, a non-empty header group is only destroyed after every header bar it contains has been removed from the group or destroyed. Fixes #56 * Revert "HdyHeaderGroup: Disconnect from header bar's signals during dispose" Since commit c5bf27d44022bdfa94b3f560aac8c22115e06363 header bars are destroyed before header group, so when destroying the header group, the list of header bars is always empty, so there's nothing to unref anymore. Reverts commit 14e5fc7b923440a99c3a62635cf895e73c5a49cd. [ tallero ] * build: Don't use -fstack-protector-strong on mingw64. This unbreaks compilation on that platform. (Closes: #64) -- Guido Günther Mon, 17 Dec 2018 16:26:19 +0100 libhandy (0.0.5) experimental; urgency=medium [ Guido Günther ] * Release libhandy 0.0.5 * meson: Properly depend on the generated headers. This fixes dependency problems with the generated headers such as https://arm01.puri.sm/job/debs/job/deb-libhandy-buster-armhf/263/console See http://mesonbuild.com/Wrap-best-practices-and-tips.html#eclare-generated-headers-explicitly * debian: Make sure we create a strict shlibs file libhandy's ABI changes a lot so make sure we generate dependencies that always require the upstream version built against. * debian: Mark buil-deps for tests as * gitlab-ci: Deduplicate before_script * gitlab-ci: Build with clang (scan-build) as well. We currently don't fail on warnings: https://github.com/mesonbuild/meson/issues/4334 * HdyLeaflet: Remove unused initializations spotted by clang * doc: Add that virtual methods carry the class prefix (Closes: #53) * docs: Add libhandy users. This allows to find in uses examples easily. * docs: Mention meson as well. Fewer and fewer GNOME projects use autotools. * docs: Drop package_ver_str from include path. We add this in the pkg-config file so no need to specify it again. * Add i18n infrastructure * Add hdy_init() This initializes i18n. (Closes: #36) * meson: Depend on glib that supports g_auto*. Related to #33 * HACKING: document using g_auto* is o.k. (Closes: #33) * HACKING: Use syntax highlighting. * Drop Jenkinsfile. We run in gitlab-ci now * build: Detect if ld supports a version script. This is e.g. not the case for Clang on OSX. (Closes: #58) [ Jeremy Bicha ] * debian: Have libhandy-0.0-dev depend on libgtk-3-dev (Closes: #910384) * debian: Use dh --with gir so that gir1.2-handy gets its dependencies set correctly * debian: Simplify debian/rules. [ Adrien Plazas ] * example: Drop Glade support in flatpak build. * main: Init public GObject types in hdy_init() This will avoid our users to manually ensure libhandy widget types are loaded before using them in GtkBuilder templates. Fixes https://source.puri.sm/Librem5/libhandy/issues/20 * dialer: Descend from GtkBin directly. Makes HdyDialer descend from GtkBin directly rather than from GtkEventBox. GtkEventBox will be dropped in GTK+ 4 and brings no functionality to HdyDialer. * example: Rename margin-left/right to margin-start/end. Left and right margin names are not RTL friendly and will be dropped in GTK+ 4. * HACKING.md: Rename margin-left to margin-start. Left and right margin names are not RTL friendly and will be dropped in GTK+ 4. * titlebar: Fix a mention of HdyLeaflet in the docs * example: Do not access event fields. This is needed to port to GTK+ 4 as these fields will be private. * dialer: Do not access event fields. This is needed to port to GTK+ 4 as these fields will be private. [ Alexander Mikhaylenko ] * example: Remove styles present in GTK+ 3.24.1. Libhandy requires `gtk+-3.0 >= 3.24.1` anyway, so these styles aren't necessary, and also break upstream `.devel` style. [ Jan Tojnar ] * Use pkg-config for obtaining glade catalogdir -- Guido Günther Wed, 07 Nov 2018 11:17:14 +0100 libhandy (0.0.4) experimental; urgency=medium [ Mohammed Sadiq ] * dialer-button: Fix emitting signal. As the properties where set to not explicitly fire ::notify, no signals where emitted. Let it be not explicit so that the signal will be emitted on change * ci: Enable code coverage. GitLab pages isn't supported now. So simply store the artifacts. * README: Add build and coverage status images * dialer: Handle delete button long press. Make the delete button clear the whole user input on long press [ Alexander Mikhaylenko ] * example: Remove sidebar border less aggressively. Applying the style to every element inside 'stacksidebar' also removes border from unrelated elements such as scrollbars. Hence only remove it from lists. [ Adrien Plazas ] * leaflet: Add the folded property. This is a boolean equivalent of the fold property, it is a needed convenience as is can be used in GtkBuilder declarations while the fold property is more convenient to use from C as it enables stronger typing. * example: Bind back and close buttons visibility to fold. Directly bind whether the back button and the close button are visible to whether the headerbar is folded. * Add HdyHeaderGroup * example: Use a HdyHeaderGroup. This automatically updates the headerbars' window decoration layout. * dialer-button: Replace digit and letters by symbols. Unify the digit and the letters of a dialer button as its symbols. This allows to make the code simpler by limiting the number of special cases. digit. This also handles Unicode characters. * dialer-cycle-button: Don't make the secondary label dim. This helps making it clear that these symbols are available, contrary to the dim ones from a regular dialer button. * dialer-button: Make the secondary label smaller. Makes the secondary text smaller to better match the mockups for Calls. * Add CSS names to the widgets * leaflet: Document the fold and folded properties * dialer: Set buttons checked instead setting relief. When digit keys are pressed, check the buttons state to checked rather than changing the relief. * dialer: Add the relief property. This allows to set the relief of the dialer buttons. * header-group: Drop forgotten log. This was accidentally left in. Fixes https://source.puri.sm/Librem5/libhandy/issues/47 * example: Let the Column panel reach narrower widths. Readjust the column widget's margins and ellipsize its labels to let it reach narrower widths. * example: Separate the listbox items * example: Let the Dialer panel reach narrower widths. Put the dialer into a column rather than forcing its width to let it reach narrower widths. * example: Enlarge the dialer label. This makes the dialed number more readable. * example: Let the Welcome panel reach narrower widths. Let the welcome panel's labels wrap to let it reach narrower widths. * header-group: Sanitize the decoration layout. Checks whether the decoration layout is NULL, empty or has at least one colon. Fixes https://source.puri.sm/Librem5/libhandy/issues/44 * header-group: Better handle references of header bars. Take a reference when adding a header bar, release them on destruction and don't take extra references on the focused child. This avoids using pointers to dead header bars or to leak them. * header-group: Fix the type of the focus property. This also fixes the types of the accessor functions. Fixes https://source.puri.sm/Librem5/libhandy/issues/46 * header-group: Fix the docs of the focus property. This also improves the documentation of its accessor functions. * header-group: Guard the focused header bar setter. Better guard the focused header bar setter by checking that the set header bar actually is one and is part of the group. * meson: Require GTK+ 3.24.1 or newer. GTK+ 3.24.1 received style fixes required for HdyTitleBar to work as expected. [ Felix Pojtinger ] * docs: Format README to enable syntax highlighting. This also adds code fences and blanks around headers. [ Guido Günther ] * Depend on generated headers. If tests or examples are built early we want that hdy-enums.h is alread there. * docs: Add HdyFold. This makes sure it can be linked to by HdyLeaflet. * HdyLealflet: Use glib-mkenums. This makes the enums clickable in the HdyLeaflet documentation and makes the code smaller. * HdyFold: Use glib-mkenums. This makes the enums clickable in the HdyLeaflet documentation, HdyFold usable in GtkBuild and makes the code smaller. * HdyHeaderGroup: Document hdy_group_set_focus() This makes newer newer Gir scanner happy (and is a good thing anyway). * debian: Update shared library symbols * d/rules: Set a proper locale for the tests. * Check the debian package build during CI as well. This make sure we notice build breackage before it hits Jenkins to build the official debs. * ci: Fail on gtkdoc warnings. Gitlab seems to get confused by the '!' expression so use if instead. * tests: Test hdy_header_group_{get,set}_focus * HdyDialer: Apply 'keypad' style class. This applies the 'keypad' style class to both the keypad itself and its buttons. This allows to style the buttons and the keypad in the application. * glade: Verify catalog data via xmllint * debian: Add dependenies for running xmllint. This also makes sure we have it available during CI * HdyHeaderGroup: Allow to get and remove the headerbars * debian: Add new symbols * glade: Add a module so we can handle HdyHeaderGroup * run: Add glade lib to LD_LIBRARY_PATH. This makes it simple to test the built version. * Move glade catalog from data/ to glade/ Given that there will be more complex widgets lets keep the catalog and module together. * glade: Use a custom DTD. Glades DTD is not up to date. Use a custom copy until this is fixed upstream: https://gitlab.gnome.org/GNOME/glade/merge_requests/11 We do this as a separate commit so we can revert it once upstream glade is fixed. * glade: Support HdyHeaderGroup (Closes: #38) * debian: Ship glade module -- Guido Günther Fri, 05 Oct 2018 18:32:42 +0200 libhandy (0.0.3) experimental; urgency=medium [ Adrien Plazas ] * New HdyTitleBar widget. This coupled with a transparent headerbar background can work around graphical glitches when animation header bars in a leaflet. * column: Add the linear-growth-width property * glade: Fix the generic name of HdyArrows * flatpak: Switch the runtime of the example to master. * column: Add a missing break statement. * leaflet: Hide children on transition end only when folded. * leaflet: Init mode transition positions to the final values. * example: Always show a close button. * example: Load custom style from CSS resource * example: Draw the right color for sidebar separators. * example: Use separators to separate the panels. * leaflet: Start the child transition only when folded. [ Christopher Davis ] * Add HdyColumn to libhandy.xml for glade. [ Heather Ellsworth ] * Add issue template [ Jordan Petridis ] * leaflet: initialize a variable. [ Guido Günther ] * HdyButton: Chain up to parent on finalize * gitlab-ci: Fail on compile warnings * meson: Warn about possible uninitialized variables * HdyLeaflet: Fix two uninitialized variables * Update list of compiler warnings from phosh and fix the fallout. -- Guido Günther Wed, 12 Sep 2018 12:03:54 +0200 libhandy (0.0.2) experimental; urgency=medium [ Guido Günther ] * Use source.puri.sm instead of code.puri.sm. * Add AUTHORS file * gitlab-ci: Build on Debian buster using provided build-deps. * arrows: test object construction * Multiple gtk-doc fixes * docs: Abort on warnings. * DialerButton: free letters [ Adrien Plazas ] * dialer: Make the grid visible and forbid show all. * example: Drop usage of show_all() * dialer: Add column-spacing and row-spacing props. * example: Change the grid's spacing and minimum size request. * flatpak: Allow access to the dconf config dir. * Replace phone-dial-symbolic by call-start-symbolic. * column: Fix height for width request. -- Guido Günther Wed, 18 Jul 2018 13:12:10 +0200 libhandy (0.0.1) experimental; urgency=medium [ Guido Günther ] * Release 0.0.1 [ Adrien Plazas ] * Add HdyColumn widget -- Guido Günther Sat, 09 Jun 2018 09:12:06 +0200 libhandy (0.0~git20180517) unstable; urgency=medium * Add an arrows widget. The widget prints a number of arrows one by one to indicate a sliding direction. Number of arrows and animation duration are configurable. * Add symbols file -- Guido Günther Thu, 17 May 2018 15:51:01 +0200 libhandy (0.0~git20180429) unstable; urgency=medium [ Guido Günther ] * New git snapshot * HdyDialer: Emit symbol-clicked signal. This signal is emitted when a symbol button (numbers or '#' or '*') is clicked. * HdyDialer: Emit signal when delete button was clicked. * dialer: Make it simple to clear the stored number. This also makes sure we don't send multiple number changed events when nothing changed. * dialer: Delay number notify. On button press send out the number changed signal at the very end so listeners can process the button event prior to the number update event. [ Adrien Plazas ] * leaflet: Refactor homogeneity. This makes factorizes the homogeneity functions of HdyLeaflet to make the code a bit shorter. * build: Add '--c-include=handy.h' GIR options back. This is necessary for introspection to know the header file to use. * dialer: Check params of the 'number' prop accessors. Sanitize the parameters of the 'number' property accessor. This will warn or misusages of the API at runtime and avoid potential crashes. * dialer: Style cleanup of the 'number' prop accessors. Use gchar instead of char, use GNOME style pointer spacing and name the number parameter 'number'. This is all cosmetic but will make the code look a bit more GNOME-like. * example: Drop hardcoded default window size. This avoid overridding with the one we set in the the .ui file of the window. * example: Move window title to .ui file. This avoid hardcoding values when we can put them in the UI description. * example-window: Make the default size more phone-like [ Bob Ham ] * dialer: Add "show-action-buttons" property. Add a new boolean "show-action-buttons" property that specifies whether the submit and delete buttons are displayed. -- Guido Günther Sun, 29 Apr 2018 12:01:58 +0200 libhandy (0.0~git20180402) unstable; urgency=medium * Initial release -- Guido Günther Mon, 02 Apr 2018 12:17:44 +0200 libhandy-0.0.13/debian/control000066400000000000000000000040741360136463700162300ustar00rootroot00000000000000Source: libhandy Section: libs Priority: optional Maintainer: Guido Günther Build-Depends: debhelper-compat (= 12), dh-sequence-gir, gtk-doc-tools, libgirepository1.0-dev, libgladeui-dev, libglib2.0-doc, libgnome-desktop-3-dev, libgtk-3-doc, libgtk-3-dev, libxml2-utils, meson, pkg-config, valac (>= 0.20), # to run the tests xvfb , xauth , Standards-Version: 4.1.3 Homepage: https://source.puri.sm/Librem5/libhandy Vcs-Browser: https://salsa.debian.org/DebianOnMobile-team/libhandy Vcs-Git: https://salsa.debian.org/DebianOnMobile-team/libhandy.git Package: libhandy-0.0-0 Architecture: any Multi-Arch: same Depends: ${misc:Depends}, ${shlibs:Depends}, Description: Library with GTK widgets for mobile phones libhandy provides GTK widgets and GObjects to ease developing applications for mobile phones. . This package contains the shared library. Package: libhandy-0.0-dev Architecture: any Multi-Arch: same Section: libdevel Depends: ${misc:Depends}, ${shlibs:Depends}, gir1.2-handy-0.0 (= ${binary:Version}), libhandy-0.0-0 (= ${binary:Version}), libgtk-3-dev, Recommends: pkg-config Description: Development files for libhandy libhandy provides GTK widgets and GObjects to ease developing applications for mobile phones. . This package contains the development files and documentation. Package: gir1.2-handy-0.0 Architecture: any Multi-Arch: same Section: introspection Depends: ${gir:Depends}, ${misc:Depends}, Description: GObject introspection files for libhandy libhandy provides GTK widgets and GObjects to ease developing applications for mobile phones. . This package contains the GObject-introspection data in binary typelib format. Package: handy-0.0-examples Section: x11 Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, libhandy-0.0-0 (= ${binary:Version}) Description: Example programs for libhandy libhandy provides GTK widgets and GObjects to ease developing applications for mobile phones. . This package contains example files and the demonstration program for libhandy. libhandy-0.0.13/debian/copyright000066400000000000000000000017061360136463700165570ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: libhandy Source: https://source.puri.sm/Librem5/libhandy Files: * Copyright: 2018 Purism SPC License: LGPL-2.1+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". libhandy-0.0.13/debian/gir1.2-handy-0.0.install000066400000000000000000000000351360136463700206040ustar00rootroot00000000000000usr/lib/*/girepository-1.0/* libhandy-0.0.13/debian/handy-0.0-examples.examples000066400000000000000000000000161360136463700215670ustar00rootroot00000000000000examples/*.py libhandy-0.0.13/debian/handy-0.0-examples.install000066400000000000000000000000271360136463700214210ustar00rootroot00000000000000usr/bin/handy-0.0-demo libhandy-0.0.13/debian/libhandy-0.0-0.install000066400000000000000000000000341360136463700204270ustar00rootroot00000000000000usr/lib/*/libhandy-0.0.so.* libhandy-0.0.13/debian/libhandy-0.0-0.symbols000066400000000000000000000416241360136463700204630ustar00rootroot00000000000000libhandy-0.0.so.0 libhandy-0.0-0 #MINVER# LIBHANDY_0_0_0@LIBHANDY_0_0_0 0.0~git20180429 hdy_action_row_activate@LIBHANDY_0_0_0 0.0.6 hdy_action_row_add_action@LIBHANDY_0_0_0 0.0.6 hdy_action_row_add_prefix@LIBHANDY_0_0_0 0.0.6 hdy_action_row_get_activatable_widget@LIBHANDY_0_0_0 0.0.7 hdy_action_row_get_icon_name@LIBHANDY_0_0_0 0.0.6 hdy_action_row_get_subtitle@LIBHANDY_0_0_0 0.0.6 hdy_action_row_get_title@LIBHANDY_0_0_0 0.0.6 hdy_action_row_get_type@LIBHANDY_0_0_0 0.0.6 hdy_action_row_get_use_underline@LIBHANDY_0_0_0 0.0.6 hdy_action_row_new@LIBHANDY_0_0_0 0.0.6 hdy_action_row_set_activatable_widget@LIBHANDY_0_0_0 0.0.7 hdy_action_row_set_icon_name@LIBHANDY_0_0_0 0.0.6 hdy_action_row_set_subtitle@LIBHANDY_0_0_0 0.0.6 hdy_action_row_set_title@LIBHANDY_0_0_0 0.0.6 hdy_action_row_set_use_underline@LIBHANDY_0_0_0 0.0.6 hdy_arrows_animate@LIBHANDY_0_0_0 0.0~git20180517 hdy_arrows_direction_get_type@LIBHANDY_0_0_0 0.0~git20180517 hdy_arrows_get_count@LIBHANDY_0_0_0 0.0~git20180517 hdy_arrows_get_direction@LIBHANDY_0_0_0 0.0~git20180517 hdy_arrows_get_duration@LIBHANDY_0_0_0 0.0~git20180517 hdy_arrows_get_type@LIBHANDY_0_0_0 0.0~git20180517 hdy_arrows_new@LIBHANDY_0_0_0 0.0~git20180517 hdy_arrows_set_count@LIBHANDY_0_0_0 0.0~git20180517 hdy_arrows_set_direction@LIBHANDY_0_0_0 0.0~git20180517 hdy_arrows_set_duration@LIBHANDY_0_0_0 0.0~git20180517 hdy_centering_policy_get_type@LIBHANDY_0_0_0 0.0.10 hdy_column_get_linear_growth_width@LIBHANDY_0_0_0 0.0.3 hdy_column_get_maximum_width@LIBHANDY_0_0_0 0.0.1 hdy_column_get_type@LIBHANDY_0_0_0 0.0.1 hdy_column_new@LIBHANDY_0_0_0 0.0.1 hdy_column_set_linear_growth_width@LIBHANDY_0_0_0 0.0.3 hdy_column_set_maximum_width@LIBHANDY_0_0_0 0.0.1 hdy_combo_row_bind_model@LIBHANDY_0_0_0 0.0.6 hdy_combo_row_bind_name_model@LIBHANDY_0_0_0 0.0.6 hdy_combo_row_get_model@LIBHANDY_0_0_0 0.0.6 hdy_combo_row_get_selected_index@LIBHANDY_0_0_0 0.0.7 hdy_combo_row_get_type@LIBHANDY_0_0_0 0.0.6 hdy_combo_row_get_use_subtitle@LIBHANDY_0_0_0 0.0.10 hdy_combo_row_new@LIBHANDY_0_0_0 0.0.6 hdy_combo_row_set_for_enum@LIBHANDY_0_0_0 0.0.6 hdy_combo_row_set_get_name_func@LIBHANDY_0_0_0 0.0.10 hdy_combo_row_set_selected_index@LIBHANDY_0_0_0 0.0.7 hdy_combo_row_set_use_subtitle@LIBHANDY_0_0_0 0.0.10 hdy_dialer_button_get_digit@LIBHANDY_0_0_0 0.0~git20180429 #MISSING: 0.0.3# hdy_dialer_button_get_letters@LIBHANDY_0_0_0 0.0.4~ hdy_dialer_button_get_symbols@LIBHANDY_0_0_0 0.0.3 hdy_dialer_button_get_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_button_new@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_clear_number@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_cycle_button_get_current_symbol@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_cycle_button_get_cycle_timeout@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_cycle_button_get_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_cycle_button_is_cycling@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_cycle_button_new@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_cycle_button_set_cycle_timeout@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_cycle_button_stop_cycle@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_get_number@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_get_relief@LIBHANDY_0_0_0 0.0.3 hdy_dialer_get_show_action_buttons@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_get_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_new@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_set_number@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialer_set_relief@LIBHANDY_0_0_0 0.0.3 hdy_dialer_set_show_action_buttons@LIBHANDY_0_0_0 0.0~git20180429 hdy_dialog_get_narrow@LIBHANDY_0_0_0 0.0.11 hdy_dialog_get_type@LIBHANDY_0_0_0 0.0.7 hdy_dialog_new@LIBHANDY_0_0_0 0.0.7 hdy_ease_out_cubic@LIBHANDY_0_0_0 0.0.11 hdy_enum_value_object_get_name@LIBHANDY_0_0_0 0.0.6 hdy_enum_value_object_get_nick@LIBHANDY_0_0_0 0.0.6 hdy_enum_value_object_get_type@LIBHANDY_0_0_0 0.0.6 hdy_enum_value_object_get_value@LIBHANDY_0_0_0 0.0.6 hdy_enum_value_object_new@LIBHANDY_0_0_0 0.0.6 hdy_enum_value_row_name@LIBHANDY_0_0_0 0.0.6 hdy_expander_row_get_enable_expansion@LIBHANDY_0_0_0 0.0.6 hdy_expander_row_get_expanded@LIBHANDY_0_0_0 0.0.9 hdy_expander_row_get_show_enable_switch@LIBHANDY_0_0_0 0.0.6 hdy_expander_row_get_type@LIBHANDY_0_0_0 0.0.6 hdy_expander_row_new@LIBHANDY_0_0_0 0.0.6 hdy_expander_row_set_enable_expansion@LIBHANDY_0_0_0 0.0.6 hdy_expander_row_set_expanded@LIBHANDY_0_0_0 0.0.9 hdy_expander_row_set_show_enable_switch@LIBHANDY_0_0_0 0.0.6 hdy_fold_get_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_get_enable_animations@LIBHANDY_0_0_0 0.0.11 hdy_gtk_window_get_icon_for_size@LIBHANDY_0_0_0 0.0.10 hdy_gtk_window_toggle_maximized@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_centering_policy@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_custom_title@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_decoration_layout@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_has_subtitle@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_interpolate_size@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_show_close_button@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_subtitle@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_title@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_transition_duration@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_transition_running@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_get_type@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_new@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_pack_end@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_pack_start@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_set_centering_policy@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_set_custom_title@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_set_decoration_layout@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_set_has_subtitle@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_set_interpolate_size@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_set_show_close_button@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_set_subtitle@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_set_title@LIBHANDY_0_0_0 0.0.10 hdy_header_bar_set_transition_duration@LIBHANDY_0_0_0 0.0.10 hdy_header_group_add_header_bar@LIBHANDY_0_0_0 0.0.3 hdy_header_group_get_focus@LIBHANDY_0_0_0 0.0.3 hdy_header_group_get_header_bars@LIBHANDY_0_0_0 0.0.4~ hdy_header_group_get_type@LIBHANDY_0_0_0 0.0.3 hdy_header_group_new@LIBHANDY_0_0_0 0.0.3 hdy_header_group_remove_header_bar@LIBHANDY_0_0_0 0.0.4~ hdy_header_group_set_focus@LIBHANDY_0_0_0 0.0.3 hdy_init@LIBHANDY_0_0_0 0.0.5 hdy_init_public_types@LIBHANDY_0_0_0 0.0.5 hdy_keypad_button_get_digit@LIBHANDY_0_0_0 0.0.12 hdy_keypad_button_get_symbols@LIBHANDY_0_0_0 0.0.12 hdy_keypad_button_get_type@LIBHANDY_0_0_0 0.0.12 hdy_keypad_button_new@LIBHANDY_0_0_0 0.0.12 hdy_keypad_button_show_symbols@LIBHANDY_0_0_0 0.0.12 hdy_keypad_get_entry@LIBHANDY_0_0_0 0.0.12 hdy_keypad_get_type@LIBHANDY_0_0_0 0.0.12 hdy_keypad_new@LIBHANDY_0_0_0 0.0.12 hdy_keypad_set_entry@LIBHANDY_0_0_0 0.0.12 hdy_keypad_set_left_action@LIBHANDY_0_0_0 0.0.12 hdy_keypad_set_right_action@LIBHANDY_0_0_0 0.0.12 hdy_keypad_show_symbols@LIBHANDY_0_0_0 0.0.12 hdy_leaflet_child_transition_type_get_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_can_swipe_back@LIBHANDY_0_0_0 0.0.12 hdy_leaflet_get_can_swipe_forward@LIBHANDY_0_0_0 0.0.12 hdy_leaflet_get_child_transition_duration@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_child_transition_running@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_child_transition_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_fold@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_homogeneous@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_interpolate_size@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_mode_transition_duration@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_mode_transition_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_transition_type@LIBHANDY_0_0_0 0.0.12 hdy_leaflet_get_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_visible_child@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_get_visible_child_name@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_mode_transition_type_get_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_new@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_set_can_swipe_back@LIBHANDY_0_0_0 0.0.12 hdy_leaflet_set_can_swipe_forward@LIBHANDY_0_0_0 0.0.12 hdy_leaflet_set_child_transition_duration@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_set_child_transition_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_set_homogeneous@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_set_interpolate_size@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_set_mode_transition_duration@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_set_mode_transition_type@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_set_transition_type@LIBHANDY_0_0_0 0.0.12 hdy_leaflet_set_visible_child@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_set_visible_child_name@LIBHANDY_0_0_0 0.0~git20180429 hdy_leaflet_transition_type_get_type@LIBHANDY_0_0_0 0.0.12 hdy_lerp@LIBHANDY_0_0_0 0.0.11 hdy_list_box_separator_header@LIBHANDY_0_0_0 0.0.6 hdy_paginator_box_animate@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_get_distance@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_get_n_pages@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_get_nth_child@LIBHANDY_0_0_0 0.0.12 hdy_paginator_box_get_position@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_get_spacing@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_get_type@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_insert@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_is_animating@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_new@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_reorder@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_scroll_to@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_set_position@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_set_spacing@LIBHANDY_0_0_0 0.0.11 hdy_paginator_box_stop_animation@LIBHANDY_0_0_0 0.0.11 hdy_paginator_get_allow_mouse_drag@LIBHANDY_0_0_0 0.0.12 hdy_paginator_get_animation_duration@LIBHANDY_0_0_0 0.0.11 hdy_paginator_get_center_content@LIBHANDY_0_0_0 0.0.11 hdy_paginator_get_indicator_spacing@LIBHANDY_0_0_0 0.0.11 hdy_paginator_get_indicator_style@LIBHANDY_0_0_0 0.0.11 hdy_paginator_get_interactive@LIBHANDY_0_0_0 0.0.11 hdy_paginator_get_n_pages@LIBHANDY_0_0_0 0.0.11 hdy_paginator_get_position@LIBHANDY_0_0_0 0.0.11 hdy_paginator_get_spacing@LIBHANDY_0_0_0 0.0.11 hdy_paginator_get_type@LIBHANDY_0_0_0 0.0.11 hdy_paginator_indicator_style_get_type@LIBHANDY_0_0_0 0.0.11 hdy_paginator_insert@LIBHANDY_0_0_0 0.0.11 hdy_paginator_new@LIBHANDY_0_0_0 0.0.11 hdy_paginator_prepend@LIBHANDY_0_0_0 0.0.11 hdy_paginator_reorder@LIBHANDY_0_0_0 0.0.11 hdy_paginator_scroll_to@LIBHANDY_0_0_0 0.0.11 hdy_paginator_scroll_to_full@LIBHANDY_0_0_0 0.0.11 hdy_paginator_set_allow_mouse_drag@LIBHANDY_0_0_0 0.0.12 hdy_paginator_set_animation_duration@LIBHANDY_0_0_0 0.0.11 hdy_paginator_set_center_content@LIBHANDY_0_0_0 0.0.11 hdy_paginator_set_indicator_spacing@LIBHANDY_0_0_0 0.0.11 hdy_paginator_set_indicator_style@LIBHANDY_0_0_0 0.0.11 hdy_paginator_set_interactive@LIBHANDY_0_0_0 0.0.11 hdy_paginator_set_spacing@LIBHANDY_0_0_0 0.0.11 hdy_preferences_group_add_preferences_to_model@LIBHANDY_0_0_0 0.0.10 hdy_preferences_group_get_description@LIBHANDY_0_0_0 0.0.10 hdy_preferences_group_get_title@LIBHANDY_0_0_0 0.0.10 hdy_preferences_group_get_type@LIBHANDY_0_0_0 0.0.10 hdy_preferences_group_new@LIBHANDY_0_0_0 0.0.10 hdy_preferences_group_set_description@LIBHANDY_0_0_0 0.0.10 hdy_preferences_group_set_title@LIBHANDY_0_0_0 0.0.10 hdy_preferences_page_add_preferences_to_model@LIBHANDY_0_0_0 0.0.10 hdy_preferences_page_get_icon_name@LIBHANDY_0_0_0 0.0.10 hdy_preferences_page_get_title@LIBHANDY_0_0_0 0.0.10 hdy_preferences_page_get_type@LIBHANDY_0_0_0 0.0.10 hdy_preferences_page_new@LIBHANDY_0_0_0 0.0.10 hdy_preferences_page_set_icon_name@LIBHANDY_0_0_0 0.0.10 hdy_preferences_page_set_title@LIBHANDY_0_0_0 0.0.10 hdy_preferences_row_get_title@LIBHANDY_0_0_0 0.0.10 hdy_preferences_row_get_type@LIBHANDY_0_0_0 0.0.10 hdy_preferences_row_get_use_underline@LIBHANDY_0_0_0 0.0.10 hdy_preferences_row_new@LIBHANDY_0_0_0 0.0.10 hdy_preferences_row_set_title@LIBHANDY_0_0_0 0.0.10 hdy_preferences_row_set_use_underline@LIBHANDY_0_0_0 0.0.10 hdy_preferences_window_get_type@LIBHANDY_0_0_0 0.0.10 hdy_preferences_window_new@LIBHANDY_0_0_0 0.0.10 hdy_search_bar_connect_entry@LIBHANDY_0_0_0 0.0.6 hdy_search_bar_get_search_mode@LIBHANDY_0_0_0 0.0.6 hdy_search_bar_get_show_close_button@LIBHANDY_0_0_0 0.0.6 hdy_search_bar_get_type@LIBHANDY_0_0_0 0.0.6 hdy_search_bar_handle_event@LIBHANDY_0_0_0 0.0.6 hdy_search_bar_new@LIBHANDY_0_0_0 0.0.6 hdy_search_bar_set_search_mode@LIBHANDY_0_0_0 0.0.6 hdy_search_bar_set_show_close_button@LIBHANDY_0_0_0 0.0.6 hdy_shadow_helper_clear_cache@LIBHANDY_0_0_0 0.0.12 hdy_shadow_helper_draw_shadow@LIBHANDY_0_0_0 0.0.12 hdy_shadow_helper_get_type@LIBHANDY_0_0_0 0.0.12 hdy_shadow_helper_new@LIBHANDY_0_0_0 0.0.12 hdy_squeezer_get_child_enabled@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_get_homogeneous@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_get_interpolate_size@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_get_transition_duration@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_get_transition_running@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_get_transition_type@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_get_type@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_get_visible_child@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_new@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_set_child_enabled@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_set_homogeneous@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_set_interpolate_size@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_set_transition_duration@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_set_transition_type@LIBHANDY_0_0_0 0.0.10 hdy_squeezer_transition_type_get_type@LIBHANDY_0_0_0 0.0.10 hdy_string_utf8_len@LIBHANDY_0_0_0 0.0~git20180429 hdy_string_utf8_truncate@LIBHANDY_0_0_0 0.0~git20180429 hdy_swipe_group_add_swipeable@LIBHANDY_0_0_0 0.0.12 hdy_swipe_group_get_swipeables@LIBHANDY_0_0_0 0.0.12 hdy_swipe_group_get_type@LIBHANDY_0_0_0 0.0.12 hdy_swipe_group_new@LIBHANDY_0_0_0 0.0.12 hdy_swipe_group_remove_swipeable@LIBHANDY_0_0_0 0.0.12 hdy_swipe_tracker_captured_event@LIBHANDY_0_0_0 0.0.11 hdy_swipe_tracker_confirm_swipe@LIBHANDY_0_0_0 0.0.11 hdy_swipe_tracker_get_allow_mouse_drag@LIBHANDY_0_0_0 0.0.12 hdy_swipe_tracker_get_enabled@LIBHANDY_0_0_0 0.0.11 hdy_swipe_tracker_get_reversed@LIBHANDY_0_0_0 0.0.11 hdy_swipe_tracker_get_type@LIBHANDY_0_0_0 0.0.11 hdy_swipe_tracker_new@LIBHANDY_0_0_0 0.0.11 hdy_swipe_tracker_set_allow_mouse_drag@LIBHANDY_0_0_0 0.0.12 hdy_swipe_tracker_set_enabled@LIBHANDY_0_0_0 0.0.11 hdy_swipe_tracker_set_reversed@LIBHANDY_0_0_0 0.0.11 hdy_swipeable_begin_swipe@LIBHANDY_0_0_0 0.0.12 hdy_swipeable_emit_switch_child@LIBHANDY_0_0_0 0.0.12 hdy_swipeable_end_swipe@LIBHANDY_0_0_0 0.0.12 hdy_swipeable_get_type@LIBHANDY_0_0_0 0.0.12 hdy_swipeable_switch_child@LIBHANDY_0_0_0 0.0.12 hdy_swipeable_update_swipe@LIBHANDY_0_0_0 0.0.12 hdy_title_bar_get_selection_mode@LIBHANDY_0_0_0 0.0.3 hdy_title_bar_get_type@LIBHANDY_0_0_0 0.0.3 hdy_title_bar_new@LIBHANDY_0_0_0 0.0.3 hdy_title_bar_set_selection_mode@LIBHANDY_0_0_0 0.0.3 hdy_value_object_copy_value@LIBHANDY_0_0_0 0.0.8 hdy_value_object_dup_string@LIBHANDY_0_0_0 0.0.8 hdy_value_object_get_string@LIBHANDY_0_0_0 0.0.8 hdy_value_object_get_type@LIBHANDY_0_0_0 0.0.8 hdy_value_object_get_value@LIBHANDY_0_0_0 0.0.8 hdy_value_object_new@LIBHANDY_0_0_0 0.0.8 hdy_value_object_new_collect@LIBHANDY_0_0_0 0.0.8 hdy_value_object_new_string@LIBHANDY_0_0_0 0.0.8 hdy_value_object_new_take_string@LIBHANDY_0_0_0 0.0.8 hdy_view_switcher_bar_get_icon_size@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_bar_get_policy@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_bar_get_reveal@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_bar_get_stack@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_bar_get_type@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_bar_new@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_bar_set_icon_size@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_bar_set_policy@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_bar_set_reveal@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_bar_set_stack@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_get_icon_name@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_get_icon_size@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_get_label@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_get_needs_attention@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_get_size@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_get_type@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_new@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_set_icon_name@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_set_icon_size@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_set_label@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_set_narrow_ellipsize@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_button_set_needs_attention@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_get_icon_size@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_get_narrow_ellipsize@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_get_policy@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_get_stack@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_get_type@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_new@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_policy_get_type@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_set_icon_size@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_set_narrow_ellipsize@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_set_policy@LIBHANDY_0_0_0 0.0.10 hdy_view_switcher_set_stack@LIBHANDY_0_0_0 0.0.10 libhandy-0.0.13/debian/libhandy-0.0-dev.install000066400000000000000000000002761360136463700210560ustar00rootroot00000000000000usr/include/* usr/lib/*/libhandy-0.0.so usr/lib/*/glade/modules/libglade-handy.so usr/lib/*/pkgconfig/* usr/share/gir-1.0/* usr/share/glade/catalogs/ usr/share/gtk-doc/ usr/share/vala/vapi/ libhandy-0.0.13/debian/rules000077500000000000000000000003151360136463700156770ustar00rootroot00000000000000#!/usr/bin/make -f export DEB_BUILD_MAINT_OPTIONS = hardening=+all %: dh $@ override_dh_auto_configure: dh_auto_configure -- -Dgtk_doc=true override_dh_auto_test: xvfb-run -s -noreset dh_auto_test libhandy-0.0.13/debian/source/000077500000000000000000000000001360136463700161205ustar00rootroot00000000000000libhandy-0.0.13/debian/source/format000066400000000000000000000000151360136463700173270ustar00rootroot000000000000003.0 (native) libhandy-0.0.13/debian/tests/000077500000000000000000000000001360136463700157625ustar00rootroot00000000000000libhandy-0.0.13/debian/tests/build-test000077500000000000000000000003401360136463700177610ustar00rootroot00000000000000#!/usr/bin/make -f CFLAGS=$(shell pkg-config --cflags libhandy-0.0) LIBS=$(shell pkg-config --libs libhandy-0.0) a.out: debian/tests/build-test.c gcc $(CFLAGS) $< $(LIBS) @echo "Build test of $< succeeded" @rm -f a.out libhandy-0.0.13/debian/tests/build-test.c000066400000000000000000000002161360136463700202010ustar00rootroot00000000000000#include #define HANDY_USE_UNSTABLE_API #include int main (int argc, char **argv) { hdy_dialer_new (); } libhandy-0.0.13/debian/tests/control000066400000000000000000000003061360136463700173640ustar00rootroot00000000000000Tests: build-test Depends: libhandy-0.0-dev, build-essential, pkg-config Restrictions: allow-stderr Tests: python-gi-test Depends: gir1.2-handy-0.0, python3-gi, python3 Restrictions: allow-stderr libhandy-0.0.13/debian/tests/python-gi-test000077500000000000000000000003641360136463700206060ustar00rootroot00000000000000#!/usr/bin/python3 # # Make sure gobject introspection works import gi from gi.repository import GLib gi.require_version('Handy', '0.0') from gi.repository import Handy s = GLib.String().append("asdü") assert Handy.string_utf8_len(s) == 4 libhandy-0.0.13/doc/000077500000000000000000000000001360136463700141435ustar00rootroot00000000000000libhandy-0.0.13/doc/build-howto.xml000066400000000000000000000120621360136463700171230ustar00rootroot00000000000000 %gtkdocentities; ]> Compiling with &package_string; 3 Compiling with &package_string;Notes on compiling. Building If you need to build &package_string;, get the source from here and see the README.md file. Using pkg-config Like other GNOME libraries, &package_string; uses pkg-config to provide compiler options. The package name is "&package_ver_str;". If you use Automake/Autoconf, in your configure.ac script, you might specify something like: PKG_CHECK_MODULES(LIBHANDY, [&package_ver_str;]) AC_SUBST(LIBHANDY_CFLAGS) AC_SUBST(LIBHANDY_LIBS) Or when using the Meson build system you can declare a dependency like: dependency('&package_ver_str;') The "&package_api_version;" in the package name is the "API version" (indicating "the version of the &package_string; API that first appeared in version &package_api_version;") and is essentially just part of the package name. Bundling the library As &package_string; uses the Meson build system, bundling it as a subproject when it is not installed is easy. Add this to your meson.build: &package_string;_dep = dependency('&package_ver_str;', version: '>= &package_version;') if not &package_string;_dep.found() &package_string; = subproject( '&package_string;', install: false, default_options: [ 'examples=false', 'package_subdir=my-project-name', 'tests=false', ] ) &package_string;_dep = &package_string;.get_variable('&package_string;_dep') endif Then add &package_string; as a git submodule: git submodule add &package_url;.git subprojects/&package_string; To bundle the library with your Flatpak application, add the following module to your manifest: { "name" : "&package_string;", "buildsystem" : "meson", "builddir" : true, "config-opts": [ "-Dexamples=false", "-Dtests=false" ], "sources" : [ { "type" : "git", "url" : "&package_url;.git" } ] } Acknowledge the Instability Since the library is young and is still changing a lot, in order to use it you are required to acknowledge that your are using an unstable API. To do so, HANDY_USE_UNSTABLE_API must be defined for compilation to succeed. From C code or any compatible language, you can prefix your inclusion of the &package_string; header like so: #define HANDY_USE_UNSTABLE_API #include <handy.h> Including individual headers rather than handy.h is not recommended. You can also acknoledge this with the definition option of your C compiler, like -DHANDY_USE_UNSTABLE_API. This is required from Vala. To use libhandy from Vala, you must define the acknowledgment in C via -X -DHANDY_USE_UNSTABLE_API. If your build system uses a two pass compilation and hence your Vala compiler outputs C (Meson, Automake, or using the --ccode Vala compiler option to trigger that) then you must add -DHANDY_USE_UNSTABLE_API to your C compiler argments instead. libhandy-0.0.13/doc/handy-docs.xml000066400000000000000000000110171360136463700167160ustar00rootroot00000000000000 %gtkdocentities; ]> &package_name; Reference Manual This document is the API reference for &package_name; &package_version;. Handy is a library to help you write apps for GTK/GNOME based mobile phones. If you find any issues in this API reference, please report it using the bugtracker. 2017-2019 Purism SPC Introduction Widgets and Objects Helpers Object Hierarchy API Index Index of deprecated API Index of new symbols in 0.0.6 Index of new symbols in 0.0.7 Index of new symbols in 0.0.8 Index of new symbols in 0.0.10 Index of new symbols in 0.0.11 Index of new symbols in 0.0.12 Annotations glossary libhandy-0.0.13/doc/images/000077500000000000000000000000001360136463700154105ustar00rootroot00000000000000libhandy-0.0.13/doc/images/arrows.png000066400000000000000000000432551360136463700174440ustar00rootroot00000000000000PNG  IHDR{BsBITOtEXtSoftwaremate-screenshotȖJ IDATxy`gf&}oNH! R[VzԷzzj=^jkX|ڷނKrnss_fc!ܐ͓+3ٝ緓oybvP!!\u]|_~SBJ`V`E$';]uL|%c`cW q$Ѓz!BUUessnw{2L7DG0 `V'q*{zz,X-bbbݾp@$NssSFFomeƤ\$,1NvA]5L$!k~JC@}Q)8@8@8@8@ϔq::;}W{T,8?Dm]]ш z'k"#8l+GxݹsҞ}ɀBHWwÏ>v2[u^+njjk"#_{ð4?]pvvvvuuC'NDGentvu\q/0 +&/ၟ3-k/x5UT Ô*BCB^},=ݐ!qò n:|ȈpSEe"`xWܲpqX8"Bgg tED&&R)Cwɺ~bcbj~nY,~/~Yne<ϲ+/O>Q!4[bՈCюXM7:&:^!*o_'pNAؘ?z:03JiY1!^QTj^1&$eq&~@AXVx_UUW??L<7h`S=;튢h]Vvc+0nig<?/W}|كA"lWE殅*qo!Jj/0qxIL6ę9,=9V@8Ѓ>3Nd2.|*G1 @F(74T@ήrrNLJL aBi8b))-+)-ߺ UUj*јhLP*ˀięDQ+.)q!==9'r<hGg@pťee}}nY\. "l,JĪꚪ?__1!ɘ"c!qf&AjjkKJkE=!)q!ʪƦI./dɼSaIȈ3g..-+-+X,nYBHKN4 q\B|\B|\gggQIiYjN!QkjjjJ1!>ɘV'W;x$ |UuMqIi}cqK!a!IHWf4fggwW%T~^~ /zb .qq1q1ndaNx#$r0 0nFQY2k j.(<]P8d wLJPQYUQYEa!8(z\[t=~$fGApn|$\. "O򡠢 VUTULCA3!qf&AC;w->OęiKJ-[P(sYTRZVnZiHŚں:RiLO2&3C<_U]S\RZ@ҧaHXHhR1:*թM@@YUb;? q.q\\lL\l:Y"ohoh80[QjuvyHZ OHo>E%%&$ CE,FGEEGE`YcSScS#GR*Є&5y\7ĚnX˚J弌d)V;]8!`2oK   uHo--s yi||sM=r`Z qf&_b0,-M!qV0y7īo3,RSRS.FZ-#58anj؉3:E>L275Za0IH1/#}*L:%RRF|`˼t {$X0?S[Z !:&>.n<S j.^h(74čwL1`R$hL2tЃz8@.Z{{G{;ЃKc(i(.)u.C&%g2pl+0O1[o'!q`Tv}S~vGQqiQq ;FpQ5}표 γltpy@!d2QGG#GeHY-e&Si7!ęDQtm'豜'F08p ^0 HYAEw=.-{zrN?y qf87>E.B'PPQkk⡠83 z!v{rO;'C4ťee- T( qɉƀ9KK,*)-+7Y4$bMm]MmR4&'jję!x).)ol n0$,$4)Ԍ& `*bϟ`E8^]]8..6&.6m,7474oVnFqa-(j:;k~V>9is&sK[90-83݂/`1N&8A nhTշi)i)ueH075męaRtznq&AF0$$w>pT&d)I)I#>0e^F:k,j -V7Kk5Ke/4UVLk`njƻu&_0Td4&]\:A=H q$Ѓz8@A=H q$Ѓz8@A=H q$Ѓz8@A=H q$Ѓz8@A=H q$#`BDlϾNsEKV UC;tZ̬omRAxjV̴9i q JMaLUU(R!q@S[c`a·O=E\ {>3lL+,-bD \Uu1u~o]R7L~":7^քOeho^*|CڸX xAh%+=r5nHZvWԛ\*u|gkl)0}}NaL*U**uP`A dHOpuln3nu)NL'2tW;zl_grM2 !#}5xouu ~hmlm,(.KM7/pg7*dcK++<Bk4ښ!"B<40V8јgLO /!Dfl]5`߫\+2#퀂g^i2־ɘ0"uw5^?# 1^ַ{Q$ՇB ^uAwtkq?'W{Mm x#q<kd΍ 3ITS"leuE&d A`zz: !rTٝGG{g|{Ht^V%H>涮rSu-aeƹP8pHDIkkS* 糃ض&MX,!=+,!G5o~`a8Db/PhX=RJCB" #R9ڌt((f&$!,K%5*(֮^H儐Xч>\L0%q<B>~j{OfsxLHH+,'(2f6,V$li LZfuUG|# azz{KWL 9;V}VvEG]9Bˤ2^kgLs\quw~fY^ UcK++ ȵ610jjmaD8:R3PAIUcꎎvU~~6px.Q*L_u"}Zӹ+R;a؄q#Lc.,#.|t tF 1*\uvZl}vV"ᄴĔ N[:DbwX-ܹC Bb"`łrRRNaOib"CB#8}1耎$Xs'RdUc`(q[e"!A,ˆAd_js=&^Z[Y'h**榖V4q!ł 7tQŞ>KyeWWPGCsWװX?G,2">uE,òq2"q:C1Sԁ>'H1h4 'j5ڮ6Qd-nvX&@%+#ćEmjjjZȅu-Q i}P6H%(W6**.Ep)jOkD<Q̓^ϫT,!U[+DN/Q՘0Dojk;9 $$ !N^46 gљ4[y,Uysx&aҸ*Q1KD}צ޳]BQUb0 tZ:ڛ !kݮ sL~ %KN М .4`x&TU5l76O4uvAપӲ 7ɖ)ͭA{D=T8㝄Q]Mj?B)U=⠤pB!6wjЇ >J%qv_|A rDv'SWW9a~ق,AG4uݻk _^]ɏwŒpe}ˁۻOʘ0M찝7 i|,.X|ȈSkj(FYɞ {U?dV^2 L(ULw[:FBNAzjUh/`tJQ1 9%Z4zqpagl #O5.]@8r`D\thGEƕ9(rЩ~]eM]mmmG{[ESq1ח3XVh 0^gHrUkk[[[[wOw?t"ʤRWa1;w~| ā{33 IrsNU=H q$Ѓz8@A=H q$Ѓz8@A=H q(rT8X]d&R} !<7xaŶ+0ܤ 6E/|@D*O{ʧo==8SE\(6"bF|( ̠g t+1&"l:;˙:p?Ũ˟+8M|ū٪ Ԓyv;z9UL.留>zepfaVD,m-?x=_VVx׬[6 y^QYYI!pH8&!*$cKEdj#R]5 A =~Q-?OA:z#OILcb 9'k粅5K*MeE> ͜pCo:$&]r+?qk>zl O~{XA W?;|yN1d-]}NCpZfk \![¹񁆸Suբӡ/ZzR?JV>!1|3H3,qSYA/3ddiES[_sՕWƤj'_ 7~Z wCV\|kY%~fs&HH[:-E9׭^p =:ʕk8OIUc&sP?lN?e~Ds萊njogwuB|}$k._Tzl_ا*7f.͜jN0&B+Lj IDAT7D^qg>2њ#qW#P;fuܧ{ϡ/wDO!ܸk۵Ǯ9WR²{ <+Gc"?9n¼ތs47V%V1<'19.FPltwged\.HϬks*fs.Xz)s뛚mjnEB8Dtإ68H3!ĸNC 1xkok!s|(ܜԹß~mi7W/Z(%}~J⯒ǧ4&jht7ed8\ƎXDqG06Z"!*}]]>DֶkGGwjafDò$#%N:̣QF5f6cQ^mFqJ*T2Qo I8/MvG1UI!Dox..Ӈ:sasv3WB^QPV|@2=-94JBHp)>\U$rRS"E" q;YC*,'lX W٬2sjZk\q뮹z5Wmݽc1_ =uBp:lĩܰ`BAjk PupF`YBHgOsS;9Q*>nꐒBQdϔA#'٭BHgOY"XTz1È2g ysEمÿ57h+d8 STQFSdvã)&%PuʷٝUuGFci(/ʓ͜>N<.T lA!2SI|od~ղeGOX"h^:.A{W?'$T7B\uwfS*V,Z*ѵ8fT* g2 r颜y~;U~>˲k>ٳG*eCvx0')X ]qae)~Jy1\1pbmtHV-]*&Z')&`ʥhW!KCA1!d? UGFh.>>b7*O+\̚ނ`՛{9٦==\'D'MrQ)`fUdttuw2X|鼸 3Sl{!ZtD2/<[̌O1!qBb‚c®*[!ƀ* q$Ѓz8@A=H q$Ѓz8@A=H q$Ѓz8@A=H q$Ѓz8@A=HHܱk+wnwHnejK };EBN9.Bǝ38bۡL@\\{_㺍,!Y;~Q ;B >S19m}l^'yNiCSݼ&QEZw>vz_=Os}k?_o1s|oߝWK!Ƶwxn+8Bޯ l"w-DG鞽\ۚ&,Y6?o>Ɨ͊Ʊ ?{cSU99s8WmŧK,;Ud{+x…ϛ 2;mj&`LW!%R|&xMxEjg/c;͕v"MǾg`&=IcKKC ~EAMdusXrf #Wk ak6ަX8~fϾڅ˓e\e:54csĞ6HoD!H¢9B6dgym ujNzHl}e?^~{rˉƑ`M˃fCl`v瑯ZtM1F V2M3z_,%KH}{3߳<5#IFVu67zg>uh'L]G s-Ih/=Qj97m+֌hcOVo:qg|֭K!IoskR5lj^e#aəi !Βw|'7 Q5{jk.vzp׭N!=z?wSnnY#!& -#-9Ǥ_&rA !8y/:9_qwn2KL0;?]|kY[$' =H q$Ѓz8@A=H q$Ѓz&u$oh3;[w$b׮јW q$Ѓz8@A=H q$Ѓz8@A=H q$Ѓz<&qֽ"zbPLu a+7f3-Yy ,Q1l/}~w:D.G~mT|N8?g}QʙrontԞ셛 GYW/Bu;MV {D.*H$2e`xڕ{wK/ዞ[Xh~jYke_'Rkab?߹* )oR`Y3U7]Љ3oy3׿[vm|⨍/?X:iju5/~tN/z]Ym6nsٖmacyŇr1??|K߮yQ<STئy4Lg[(ͺz—xs/ݽ@#%Re{m^}9ȂX3#Fᅴ7~b'ȶ\#[|/p!keUES'Ih5Ã5|}u=)-nSrM^,Ğ-rM".yq[rU\|C笽ok|ޱcoݞWGfn| +!{ǧ~xao __Ю}j^$wlpxL6w?}_, U7z?~ nެР+^޺wD_P0;w~| ā{33&]35n| ͪ.]> 0z8@4q"} q$Ѓz8@A=H q$Ѓz8@A=H q縱-ul0 qK&U=H q$Ѓz8@A==&AJKKm6trEXXxBe<8MJ{{{,YP(b67tzmj*++MLL&^xڴ73BHK[__GQ$x͆q#‡)A=H q$ЃzfJEOgߞ)mDlm@SD1q-W+IDEK^0 ޴/S.5ىJ?,Y?%vW[?UrUt˄F19EE՛+o/azó'= ?ywjk뫳Q$T|j̢kwi޿2>Hgw[g(}Cܱq~?hץnx&Wbܵ2QR^^_tǚES2gOn T"fL;nMOZҐ*k+7-O VIW޻uV>89ё"=l]{tTR8Đf!Dl;Dy趿7E/< 5F50q<?EĿ%?d;+Yv~k[s_dioh|M?~Z BՖ?z=mZ{'.:QXzoa5]f}m?Ի7sxCvD.t퓟4tu7?HF߭!tՂ"YwY$|}慫g>/k:::~s -Q}ZDi柯OwAp!w` 7|g=3l/{T]OE縥 7cU{['8djtWBժ\Yapܹ‰Q_]o{~uJKƽygّkV BsY?.26.Zn,-&""*:v9޾e i W\盖C{N^nUѯɍ]yEKmo_#4QCmbq6`zCX7MwS%%f#>27i`׊es2BwgEMuu< HtW褆0b=vT}FXAk_mHKk+k.[2+Tu?xgH%+ʤ탫 3^CܜU+n߷+ׅ[: $W*$[έ[uسJJ6Pb nջ~``7ᡈ[ٹ.~DB^~梏28ϼԖQ+Ko!M&H[G*K IȎ^Q#ogpcXEKw$$)Y"v~mˑ!y=+"Y.qem/~h_jB;.j D T,!؎oEm=M˹RqK2m%ۃ;Ə=wK[t{dJ/vUʻg~HPe޲wDch?=S܀H;6qe?z[]~Ogpw!+R,_y-/˖M1[joT !U˥=W.!"Z?~;iC#uyB|ܲm׋k&8T0;w~| ā{33/b27uLMa#ص3<>ǗWuQt׮.*"!' 3'̔ q$Ѓz8@A=HD.[T=Yr9[z qEZg t~xx8FqxcyyÇl6t&K.WǏu9C7aYhL2Dثz8@A=H q$Ѓz&{qcc[`:Mv ثz8@A=H q$Ѓzpgu, 44i SM.W'$Y^gɒe ']Y{oVTYYib"gc` su?[>?MSW0o}oT0O#s =Z\4;#!:yv7½ ,?le|0N. P,c{õ,k+7-O VIW޻J~`,{7OOqKgOn T"fa. Aa?;aw}ewM /_Z7HV;,tG픫r\ڻhֿ/~*Y۟/᷷=[?UmpS˶m94/jnvX^߫7w%YPΪ[⾇?t탈\'?)hn<{vgx"['NWxDYyy{?oa5]f}m??M}cܵ`?mNoއj3ztSؼyZWP*^i ջU5dkRͷ.zowR_,f !!keܵdpEj3|iuhT1Duæ|![B|2jywnNgͻǏRT,!w=sϖm6ls(UŲ|1Ƈ!>|ƿ\]4jfj=G!tޞ^=n EGUT41x[p##_mˡ]Ouí+æs=F惘ʤn'`5:(>ZٱTV#s]o}/K]do̽ ,֟]\dl17vUƫJhnlQPqܸ5;jg*OGzϯ8UEΟ4P؎lyl9CB}=ηwT85"Tu?xgH})IDAT%+ʤ a"#|YAk_mHKk+k.[?,fBB}\<%twv_23-TfC}ީ6q!NXSQ.&\O FoKY?s*ϥ Z{&!9Lk[e5ALMY I7GvBWy<B$)ߘt?# ϳC*\9/u8wHr3Y6@DauD4'~яmd7%s=[>y#_ܓj8'_Wv嶷.?=|cE+e[e2/ R:}@vUJRxǦ? WG_'I{p seW\yI볤k+O1/mUwIX)j >>{||A$D_\[p/dK8\|rΝ._5q̬97uLMa0KlTxKثkgwV9۝8a{VB ]]'Fd#9ꎛڄ4INJ:y< =2cYҟy'y688h$*؜ߎ|~WFσy:)1^!q=UUb/' W^srRR}Co}#%H]h))-MOK{Wڀ7^{%=-lDV~1?P{|j>+v);UaFd#| !{iÏ=^RZ6{"ăU `O*I 'hgÆo$Ƥ o;~Ux8 $Ax8 $Ax8 $Ax8 $Ax8 $Ax8 $Ax8 $Ax8 $G!u0|fd6.+ /8((2""))W`8ĉ:JԵ:ӥzCEeՁCqS&M .$y@3Α5VVfgfΚ18++$NsmMR"5JΜ=שׯXT>IpؓܽqӧiΝRWW1JJ*B^kjKJˤ3PJ=&urt1o8}>f q$@7q@$@7q@#-='"Ct*_fmCcF6ƥƥgMYe@8cfuU~Z X.wrrRr~eF#CBa4 i$4773nWuM}ih9Uu;]NXj&2i[(%.b@a=\S2B82 R 5,˰3 q&>:l}(8!)1u1 \Ggk_ܨsh)Rb1^Q #hm 7]-[ G+p=XpeUul\~vJ:@wKu+, AQtԀ,U]-W~0g+*7BNb𝜄7qreϼ2 tvt8}Mb؋|Y1'6C{S%5&ˎԴf]MuuNu$8J73XmqP1,%4ՕkkzVP~|Ysn,Lʺʺ+Z Uej;)k];д `f:kʄ+x/U`K2ykU^D`3f0,(Fj##¢c?uta%:;\NZ2>+SQ Zb1K*Jj06|3wə&mA0;v=a0 q@{Ш8AP|e'E&B & z:`-ٱa͝mZywZO,tѹs($in9mJhd!?Q_WM]Έ}teF;6u"̎} -~>Lo_oIKGsbV*)gc CHbK"Ccctœ 4ak,ZƩ}u53ȟ)*_0wO@ޞ#'w퉍 FݰuSl]y˔.]Oz.Z ߿?z99VT}kW\dz!Lm}%}'}by.f_\bYh߶{grMHYMf&B8$'$Ʃ e:,"kziVj.첶TșP,wCgK)ImIߐ3q*)4jn¸0!49>7@K Pf74[hyG+T4"2w⤲"69%*U?X fMw{+R)*!P _ VZ)~}wׇ(u栏_n4JVtl~Im:wb2,tZ7 đ#**KxBŗ'e .4g2B(!2'"Wa9ấSKW"*bٙ6]X9wNHd,auUM䴝N~>%s)̙u/7nNZ=H=,/3wҚJ26*? dfN:RFE:*-0CtmckKMG ?aݻwL K;0Ux8 $Ax8 $Ax8 $Ax8 $Ax8 $Ax8 $Ax8 $Ax8 $Ax8 $Ax8 eP}v}OUs~#,;x\qɹN{]\]V5m.2i⬥7$8q%mE&]j4.Yo-t^{~~Y%^nl;tTF&Yt+ !u<@,\"w?Ʋ~@QUF܅޸||{:5U{NWKoyYv GG欧>xsc@~_:.{^0P{zG8t,z㫷 JHQ[GCɝ'wO.hG6B!&"9)T~"AoMRS更2Zs|kzN!̬,SSS}I^MόhWZjc2&uŇvO,Y[ ]|Ɣ4eWMq7o.(z- &$ʹjJPO>}mCsE;O ͚:))==-#;_?(@>i5-[SB/sƤO/ .nϮJKOQӻ'j 808vxm*{:q_q?ݯP+tfQ#绚vOڵ)eB"8L-Ƿ|k/<\ImM;e/'?bOR8B(!T9?}% !DO[cnWϚpvuJboǚǯ%]6}xkZBrW]Pa^+&G**~ۇ,!UڒsŮwo+ <>I;wAk-6a8BWvu|e gUso{"MxxwMz_PvW¨/v3̉Ee)/iݳNT9 fG\ B7k1?=qˤKpҜ>d{/wM^|V IDATBTi]O>Y޿\]C=g̰翤&:6 Sфđ#jn~e}g8CU_NT;yhζS~eNi2/2q<5SsBSRYBi?zל@Mo}@m޺ѭ 'nN!|ۉEFD9|[ok"´JN2b="&|Wv|į 2ҳUNe_߽(£~rf`@ uӱ'?:K~C64,Lvv4ֵ[&`lǃs%n aj(-i 2ʻ\Bl-%-~ J6wi7#ϔ54[\ _]T֤ӗ]bfܕd/VTب?45gEo:Ć̿mO?tr<kn^Dx-xddl=JjN6>D7Z-EE룣cY }s 6zI)SlV,Iròlzzfa6 Cl&qN' \~Q݊7wh0 iG3bC7wh0H ɾ0+*[ ۰iW@0xW-斚ҒNܧ+/2L,ͥ O;WZQn0[.F:a s'ΕW7wuFgվAqYg͝O4r?l1]S1H>fڌŅ}0>}wk8یƎӇ|aUw<ȥI̫BV`T|(t zRpn:a LRXo*u}Z= ŧek८t4GfN615F;cwq7c~û)˞(#&OO U v5M婣b9]-iYNJ!O q(fUmrɡ =I9rnMRGe#Ӓ@)aX۷{ѯ`iscشQMc&lœ(r:۬؜/c~Ώy26?^ ޣoin,G|lXJ6Էw_3}٬]_w@CxWa, 10֬|Cb"Xҗ8Ri^s4z^iaޕ4_@ے4I]KV_r!F/vSG] }T;%lVet3cWq` ۿ7!nY}Po|5 w/c5BN2=[s}7q*&,ӂ^7*ے42(rϾ_{U gio M|*?=&iD/KJU >Ѷs~܏\fom(emi1V=uî˘[kߏY{{7GC&t?]{q1Åθr d! :aZ7.]CGhٳ)t9^<K ɊtoNjk꽌IZk47*bfSY;eU.=QO^z"C 4|dhƅWDu/HhAe-o-O?y:k X>aw 9f 0Mjֹag##coUѮ3۷U[ 7}lyӢOJUe#U}[ϏdePaKOn.~/oaDnh9?[Wz eʂ 3P1:UzLh>=7Y&{So.:nhQ}*fBòP:ԥ;w1ʫ*aRCo㇆a="^vYWͪhar%U\zU7O?QTR߮7SgE%$gN6kɂ)ႡdSa4a_7-ٿ+ .~vGWsYw)SfλNTp,˲,B .lVW`4!.ѷ:D6 6fwg,@߆M gU }4M.$Heφ &Uym4@xm4y#rߘNQcE+1 ׅ8]BV1c\ʰMm'T5Q~Z*nQDp@RAN 1o7 B8ֵdނNwO\D`5mT`aDŚ *f+uPfI|6chkd\9o:@G)$ëŬE iݺwWQ i-B~ 90"%%2 HHʡtS»J95"XB##dD1"JHOR%yb^c#nꚲyҩ&d#`۾g*GOѢqwfQPڪnݹagϘKC'g&DƋV8F̾YB[kӞJ.BCsf Lu&dhSA!b5k)uϡ\GI8sq[e}cnf|bDBHusWEٸԐR I[K *?-!Ls[KdhbVDi1wVL]apR/U00Rt-F8ޒķv`SfTv**P*?cV5RG͚M*Z-;vBX5LJ}lk?8Fľ+ۢs^jBMV'"y}𜎘;֗Eߩ%dQrxk(xàoÀxm4àoÀɇ4T*].ql]xW*/ P6zIpZ-Irc[[[bbb_ }s 6zI֓' V$ȍObbrTe6 Cl&q8qĉS$ٺB߆MRă q@},I RTt>::v7wh0 iGh2efX̒ 7,˦g|?@ܡi0Ħit ;RE;w$f՝ӕv/gѮ7v(% .ԥs\[8q!'39CmJ#&?62oZKcVEA B\ :v F]?nKPW9ߥ6rѷT䚘xSqzR7%л ґcB+FVۥ-A6PY)&g*wB4~- -!āaK{?~z??9h6,;3;!X8Gk.,}mc'LY&n͖Gw;d/"LPV#]ыR(lu/ KɚM)kwQg^LZ%z9_ٱj?\U?fbT݇[zw8Ftʉ7:x܁ ȹ9T7{'"y-1D^w%0P*X[JxV;_;3B!xGVPG^g}07,܏k7^z°Uw=z\EGePw?.5e1s~8M^5U]EU\ Q:&}c+<]RQSa؜%gʻjINo2$RĭmfۍA>!7oYFo0K8.}s:'-IPp e*!,uW@q\a6]Hʝ:m&$ӠoÀɇ*à &qڲgC߆M̪< 6 h| q< 6 h|H*m]wlߒnwLoY*By^~ɱo} I7S[qF^ɮiGWuWW_}ܽGч5%PK/9BZ عeۖjFvM21Y遂W͛ ´tt@ߧ{|3ioC(!$7 rUq &e\AoF8^\|Ul2 B dyu^Ok-;6 PDSZr渪9%"H Bݟ/}H]{ONf=Cc28MhT8ƪv{bDʎrڊ9%"X嫥踌ڊԬw1cX}|J-v`j˝Lz֝(! :RJ)4Zu>BDƽ#RB䪚˜NWB 22l]}b z >FwġlJ}Tdɼy]N 63y)H8}tq;țp\-q(qa[w PJ}PJ.PBhLȿߝ9N /o D++9'Ua:>-?4]K+6Mcb3RS&dd/.f)OM(ojwXV;_W{..6ʫiK8WCOq0?= 'H-~]@!';q{Ӳg[v"(9 xVg'⬊P*LHO$|_zr~JN͙\.*\w-Cț~EyyB,k[}*'!Zw\P7Th}3&Niժ%]īe_ַ簬vQrnTt6za^ߏ l݈ת VZRV||8-->,-~i]E}4M>IRr8N@'Jˠoi0i'88>,,\V_~i/`[[[bbb_ }s 6zI֓' V$ȍObbrTe6 Cl&q8qĉS$ٺB߆M9^că q@},I RTt>::v7wh0 iGh2efX̒ 7,˦g|?@ܡi0Ħit`;mfh:Rub߆&y'nuPD;*-T)ϔ|O=nQnSݭ|UUF|Ms޺`f.IDATIV?ޞt8gIOQBZv|麉{SJ{s9o3l>Pź}_F5~UT)fqnÙB!.' bot)R',F9~zcTM8r}MQ{'r7Km5|v yAM/L?>vVVtuZ*$G` u0 j,:S[fU@[wot;,v댊 qM)RB{My!Ч6Q_{r인Mc*ީ Կ7eNGi|+̪Wlt}FuD̨.$И5/xv(M=EuTM<|s6guyDbLtBiiuw}/whWg$cyxY>IK,lҵ676Q̽;VYjBar}ymߛs2'%o9ų*ںsÁξYy$5Gջ E;jFGqݎ]|ecco UUWz^8-{6׻fǻ<`FE}crrEgɧϯજ$gn8/37ej' 596>9ɣJyǔqQeBlGZlXƸ)N{Ӥ' Pȝ#;D:x..NPf$F) U]B н{_ov^897(b׭Y~ #Fi̮ç#5aqmz-zjFw^h x֕Vdq^bG)q׮yS댴{)Sg.98PfIFJi޺uǮ6}D$E;[&d$gdO3cJh;O5 hgb_KS_d "#"+O6%CJJtQ]@)!t|j[NJUo0c\XL#n'Nku~qk2%Tamgi,B}s# }yU}+\bh#GXS:ƱrU"Qmtav,q>y8qbfUi)4Uk7X-V]:iR{2660ե' -/۱•Ϙsz#k*c`PG=3 Qr R*ʆmS9˧3&iӮlk&LĀܹvOwMUJNQ_瞛Zz Ksc_9_0tqM|x疔8}rKBE_{[8tF-bϙi6[En Zql!77/55KQOCnZ 8.W=PK{8[,8C=|:$Ieeee\[Pv* Aqq88`>0Ɯۥ*Dc삂B7@nZ 8Ng('O\ M(PP0,OCnZ 8n|b8 \ ,ˣFTT8qb!ZqTU4MY{=[,8C=Vqtg -BK4pE$āo88`̪ rKB8`wpͦ]5.[g .1n_1eh:>u2/m#gZ[ꪷ\>zOuR_|_\v_ʈ'cDڑ}~F$<Ɩ,ޗdM̻Һ#_bqǻO/ȉ7:Pد_,`g?-|n{7@;\UӉ1o6DdQVj^+"W Gyٍ7ϻnGڵ^yʖeE˧bj'RoNɖW~_bkd妺NDT%/z噈#xUUNDa%݅yWшR84d}\t~o}yŨbFDOچ#_ߺ{1u1ڷWEOTڃK~syzTc_O82""u0u^a_LٿN#"yťB*k#H8~`Qb1,Kg޼/k0""ߑ:x ݫFە@(Ċkݡ Ks'^♳&㙭 #"bѺ ).Jmبtj$ew}sYMܴn^جB,r1"Ao[Ys Ht"xh7gW֨;rziIz:Mqqљ7UW6";f;%$\*"b7쵕ێI$1FDz֗鳿wc#csŬ˃eu`` Y:HV(v > KƔFP[eQT2$B͇7+Y'߀ 6ag_*sͪ"B+~ɧ>۴8Vmsud[}v@Ȓuќ9pqLLq3M AجV[k,0"sw FDDݕ5F$GsxܭDSbDd>rokV,o{fY~W? m<?ywT]u9ܾ/֩.ΜdJN鍇t"u1FļG4D$1xI>sqrMm(]=!_dK(hVvfDtvMK3' 9X5,}[!Ii#2)u"];3YRGͼ_asOOs[rVNLS1h`;% %[c)G'=d^1ˆy<{v oDDъ{kDDRۦ9\,&̸Bܤ RO7oVYᙕ)f2dh,+ۑa%隿hݑN+,R_qSKW~XWR9Lrգl}p2Ɯۥa f9|:յ+]?2GKvIToiNg('OqQ(PP0K|:;M2e3p)4|1ozv'á`0`Xm’eyԨ8-^+UޡozWqbi=Ln y(B7 \ >l`~c^;~]Y-Y80p{ %tiχji5-=R@bL~$Pb贽yd[:N0Y G@_l7DG;ZW50ÑtQZzGft}9 =߻iih4JDE5U-|C-z7V{Fܡxl+I;H_i ʹ%io$/'d;Q>o.qccM앇 bwpob"RoޜwEQ\#K Gh5ޭ?`z=^cB:ª斿ڬjH!CQj EW77tsY8WO*uwM?ƨ\mue+~32& t-Zu g+'=kQ l?ozi6ah{:zcp+cp:0:8p}0m >l@ǁ8]}h8`8DY}8dbw3d96-Z,cJi6s!;U$t; @H0|C4#lKM7(M q1R5ذ-rҿu٘6,.W=NvxX~:jY0wڂӸLErޅ qq88`t0:Aqq88`t0:Aqq88`t0:Aqq88`t0:ƱvΝ۹fСLv%: 0qD~e)?oIENDB`libhandy-0.0.13/doc/images/dialog-desktop.png000066400000000000000000000725521360136463700210370ustar00rootroot00000000000000PNG  IHDRrsBITOtEXtSoftwaremate-screenshotȖJ IDATxwxT9[R7:izH* "*Sׂ^ "Q`R'!&Bzې=?Φq6<2g滁,̙#mٯڕs*@"D"D"D"D"DVА%%%%%%%%% g1x.W(Ltt'z.ݯIOVfiz>l`\CF<ԞBrV|lckf<2#kݪU|tG$͘9+V-[ּgVoxߢiӞ~|ӏ?5.{t8G&!薯oiZbV<ߢn+.V ⸅?X^%PxvGlswnܙ/ aLj{ tu!*!W"D%/xl^_-ӳuqځ?boBr֋~hnо=>1S,w&fO?wf6o4Ϝ̤'|G59MGoOH&0,c/9r>f`j;E}v(DwiFY>\}iχ_])B4ldώ[Wրa9wU;~VZK- ezo+O G^6E2-9ڳVqpw{gt,X5O %>VN8 1)ԣUV*'&!iߝ:#?`AJJJ.΋|3ӓOBx{/Z0UL̺!3;+1j-RKKdq97oB+lgo'2ξd*V~>j!MkfjY1nKFnzƿhGQwHE\\u0=$䒘yGϻܤ,-5$WѴ  !mB6B.ȼdSVBۼes8Ue hdYj%!< bVUw~O{ڏ}uRoo_$*O*$mIfp3Y#5|-5[5]ܳ MBrFSN~y^.xy0IR].Oh!iJEӦ}S}l%,Y78~.GoYнuqNZ]]Qnl3UԲßz%)9m_]{F V~w]۶ށoJݙ+q|тB'q:%_53;Dr|Q!"qżޯ۬mSo=?{߿]yAtZBj?h`[t);qb᏾/Un}hn?;"9}ǏK~{TB{ρ=7ln>3>a(:r'shC בoBJbRR-td{z}TԶ~5 'K qC\kЂBB̷>RqeY.++!I$I )<Q5`\_8n'5,˥3\q-^ۤkcKS*],tCltP gvJ"J"J"J"J"J"J"J"J"J"U")]hhH4_FuSe"/..VqL䅅4N|4oW&qT>tQQº(]bb\^+HHHHHHHHHH>,7lGs){[Ẻ+{͚t|a{iM ~vKI]4\$rwxDsF! Jl۷hED/uԥw뺬hZ=g7>cB۵;CDGI?l}: 5ߒKJΏYZYAQ |Owe˜ ?1=[ wr~鞮-[uo?.`[G a%! Gvw{`DXjYRϷ?5q~Ϣ$6k>)әeS'K?w䤄m_=]ƤOzQKc^3ɹgDvޝ6VlθX G\RyhXxU/ V-Z\1@LϲmQڷE3e-*Ww'mtBU !~\bȅIxܲp셐f޲γ uɫCHx5,ۖr$%OݩY&D%0^8*Cݖk#]Z۷gO7Ii׫K/lYѓ%̟,&L3tzn%-|eB6'=g.wW'ɦ߿MJa-feθ2 Aڎݭ{lQzeճJj1q6<70߯]؀>|-U-^3kwʧ3MB8uwI_,@n޸z}TԶ.JW:.*j;?/bbDFm9G"D"D"D"D"D"D"D"D"D"D"l@.44K:ƉU+HHHHHHHHHHj#,7lmGcV+9gY&$IujٶkL]-\#w@T ys䒫fSV;<~$: 8ߺV +7jE2/9BR5OP|a7p͊Ҹ/ƯCjW/y#L_zj-{V˗tu"7^4Y06r +n<\ّGSjs|w/<ڪLiKFvCuɫY*We Zd4J .&͇.-ګzpmmr%?,3uI^Q& SYJs_n8zh۵ ~@WkOyut>VzZ]כąKynEK~m@mz}TԶ.JW:.*j;?/bbDFYHHHHHHHHHHH4_ӕt:%2{yy)[8jPPPPPPPPPPPPR1|&)U=@RDncvΪ?>karC-Vyю+{>w6&}277l`d0so?l ;M7f\~VpIM Sx[~b͊~Nvj-.(CM{u#z}TԶ.JW:.*j;?/bbDF]kve$r@I$r@I$r@I$r@I$r@I$r@I$r@I$r@I$r@I$r@I$r@I/ :N@CSȽhX(D(D(D(D(D(D(D(D(D(D(̢~~Ȗj!0?rb#CkӬY݆<72uX쭯O,xDR_?6j]z0/b>{c4iׯ kbgK߽IujFQQº(] Pt9ٯPHHHHHHHHHHH4_ӕt:%2{yy)[8jPPPPPPPPPPPR ̢~~Ȗk1/9'>ÕJO{rWt>Dvf4?Tr0|b+hjiՊS  1-ya&-8RA={xbmjeT ȯP~W?;c'˭%m,{&fОɛu(?C׶zvZcVrԂroHj46O|־S[RV6@cc^"\oo4z̒$dYu13wC!\^VzY8m<תRrtrQT*gWg3eh4niL]rߒǵKŧ7%Bmߤ& 2x+gMugK7j4j^EJz@EEmPĹ~kNC"D"D"D"D"D"D"D"D"D"D"l@.44K:ƉU+HHHHHHHHHH,/>7ymD^t'ߢYˠwOSN YJy>~ɐ@GC֙ûwK5Pњ7~Rj\̚#7]LHh9B9вY/׸7~K.BQQCuՕ'+6&}277l`d/+8_Ph *F, m[ 9*;{{|ÿ2LUF3UMlo.}gtnS ,Hms&~죘2!IIk_i//r1ޒ2|?vlyin9gzW̼qkWğ<ү_v,4g1Z?wmMe\/;''P|荛lܼ%jgt\|BvNu[aҊ_Z-^>HJ-v|covO1Jg º[yh!N*9x!ӄ}J&(?`WǏ 䧄4Sxk><'̜A%D&/ƴ S|b2?]t:!mU*Me0w=ڽW;UCVʷlߝR/,hX((=: IDATo=[`RKe~w_YWyV'/dXhs;íQZ ׯ[Q4U}ii)f @Hxq_; K/'>p>w6HpԺ2r GS>֝'A4gtX}T_[)m؈WF*&eR.DjDecZpTWw @;qb=&ɜƅ7l٭[0ygvfl[d Ei|iͭFO02Y_WRz:V%K~8xuBG=-ik_(!?yO9T4'|~s뽃H @ ݱ38^A6{PO5G.z87cgsJU:OwY]Br{9 sHqiھï j=-}x;Zjw~U-^;37L,񏖆61r>cF/7J-7.5p0&`l5OBtڥV+PIz>*j[Xo tYMeB @#s 2_m911Gk% f\DQU6mZt 8^P29pGsX^8^P;tpu+' 9Rwq8^P4r$rn r1#pw2W k8^P4N$rT@(!9P6W  J+ʁF :Nˉ'xT&W*[P/F K@#@cgqWƀDh,9W  xY~@(6qI<==<=ܜlmmm%IBƢKz}vvNfVV %%%5+.>AVw I',(Kw@fMoDV;:88:84BȲxɓ C5kBʁDht%Ik:SGg'٣kףO/--FrA"jq=k2Vܡm`Gɲ\@Ù,v'~zx܀wǥZhzq\Q1tH eVVV={t9|C5?w0&V*` #PLgyZ?;\+VΜaLU oUUY.$IX;k1}%!ڻ;kՠ^wtp8kyȑn~.|UehH#POI*Gߠ^ǧwKI!YOzS>#//n~A>޷UYqÇE`mmu8r`JR6:wcZ߰Q*G_ޘ|?N/&>uW}m'׊Ҵ_/Rha.l<3I.9M-,6j=[wvnܔewK7X4 5am%!DI ~xIب?6d;X[hVO~s?$iYI'OjRV84b(2IDRcz,3@Pslj@UoѴiO?>ǟ O-r.U. >r`Ss|YcҎTcPx}z>6}Jw]\tޣq=2NG|~K3]G/g8 Jݳ9U^Q\?\Z0onfMOL:yϧt]J^Ciܙʉ$'#wy2Kp$$.l=Gw~Sl~쫓z{kO'QyB\O%w;(ПIɖyKΜ3 rt]rSKgR2L=}U%)׿Y윜L9sb锔g{~uwnŪvPuws رzkʁڧ%'e؅8#9W=a,*vm땤0紽2Jv^c]>xϖ_b ۞>?[‚ω+q|тB'q:%_JvGU; !t h;mcp'Eՙ"T45/I yF%yJ5f͡G骝g˄tî>BRz>(L%kf5߲mSo=?{߿]yAW>:$d~WeɦNXluKyO"iTH\1ztu)Ў^ͿyܷyFNޙ5*Syt2FI3-h'뎪IoӦvFSH͖@}T9Q KIR?IGJF_%;ѐ5L v$PYVQ_-7ƽEś}ͣx͐ y 3;<2paLU}y M~\7o]~G_a3v4NoН,{=;,!($WO C̞hY*֎;:8,_T0$ !ڶ Rkz߁;rީv"W{_}F4c}igKek/m=(BN!'.ɲ<_ka%DBu7 pgRvy8f1k %O~[mz7s(9cY3hLʹk,˥Js0 !ڴbMV ;T@RD. JҨ$GGH;65^RҨlBNy,$km׮3nrgyp&sQu>6([c3T&4^-^ۤkcKS*Uʥuˮgŷ LByV8PcϜ=|tFff^T:{{ovAuV5IP+UH^$d/*KI_fŔAicZԗ_*3Ĝ/1x]בl4>>i+L٘wAB.]1.O% Z9Ј8D"Pza < !xW㨂?ȼQ NHkSޢ빺899T@}af"7n-i͞ ~9 ?'zeQ^XE]ד!WT뱸<PMDžU_n-'L>ro&rA( :3>EBcg;tڊBW7dڲumϚڹkwU!7+SlR&~ZH&әDUk;V@Qq\agkW'O2)9}UUxp ̔βyOg޾%IڮN6Ĥ*%r{}<5B9`,k f8^ANFV/_R綶WE"?ueQ  Թdb9G(,eZ" !,uw7k-)//2n]E9r@}RWq9:VCuȅq ̔D7oBiݺJK˪$XX9~qovAU:ΫPX9qs]wTU?7켭qQ`upz [)y"),{0JEV+[@>ރܥjqlŴZ熸xDnbeb~P?t:G?zx[קw>ޙ9 cllݱ\2{yy)[+]`yZ={F$%44,S;8sl-c{]}V_V<ؓk2ڱ.[Yjud^mTY=^%d2mkǸT T5G"5`bbۈU%ulrBvmYi|XJPe:aCkǍFcLbK׿o;'''PW1tC ٻ@^b)5:h'yKF=x]5㩩=Z+%դe !9 ==PZ{¬mޕO}6-}`Jp{MqTfVkR_zj^|ہnjD.iӏɷ_oz6C[ZE%<ߧ>~ľM߼q_;G~Wr+;24Vm-5cU9u緣CP@UYVtuݱTSC?qk֊ͺW6~ƽ[rBv}K(B~fC$o yz2]LHh96*1lפGw5gO,`r~/(b Õ_-[[ aLdPo.9n37G!JշOoʢameUv첨8/ST}#-4TY=0U쏿V뇕Y&jqѦs˟8?ggQGm5acŔ nGJ5 SZttn3]Y0 ꂚt"7\9Wm,{m7޳{MęeS'K?w䤄m_=ýbڽl[[&䡟Ɨ~¼e7}B! _揨כ]*ndH+{vR 2: OSA9o& f11y79VhݻO1(ox3"Ď=ݻ>{ M;O%)Va/.>ya\СϕE:U6gjw/+I: ܼbMC/Nvj:f٫hi>jij9Uܔ O -qJΡ)]x }k:]6ȥ[;gJKh8`l䒿(6 j+w$K+?:%o?;W0d%|{ڋٰurUa2Z򹾕íQZ M_Ee{W~wRPFo sR]l|ܹ:-:Z10{?ru!o=|6nןAnK|[~)q[7k݃Lpaԉ$[JxmKhoo**W՛ ;@)$Xaȱc۵UoSt<1TUqTa>r(?}fǶAW-ae1e0ٿ2! Mvo6={m"z~|CY26{-g2 _>K暪t"TήSg*G=-ik_(!?yO칛UsWPJ` C<WPPxvwVN],І~(b¨5ZL\t k67-xʾ[в"A!!{h]=S!$[g}Լi#zjծc_ͻԉ\O{c̈́}N|xѲgۮH)4]_@99w()9mN:tg1_NBBHz>*j[XX+N?/11"#U={06N*&Ў:u~h</,uq@qW P#-+"6Pɓm7o /(8{."q4$r@=cE?t!aFOn]_BSD"K"D#D DF,Po8 $r@C@(q/؏@t 6+u WW&MtBB HUΡ@}A"4\SVޭmP$Itqqvqqn߮wg2dI\_HrZ=d&>>7|Vmۺ89߼r8Pﰎ={tYIݺݙz@}D"4@w& 4eA..u]qHsXh]p[$)( NJ)9\3徾MqU P YΔu7vUq0;w$r@W*2@@@"4|u|E_Td~B ` 0B],_9BUY4$r@cQ3N$ʲY WdY>v"fvhHH,|qŋfoΗm!%]LֺY{ Q :77ND\R![_fo2?o=NqoQFxߦ~_wDaʉ^3u8S==\B A%[Q,Y;z{-R_(]wI7 ڬMTBcfzSP i]Ӷ_ʘ Piϐ-EVo֓y~.{W;]_p.z&1nQ/XL9qh<$^-,& 颢W%&@ddՈ1io]_(op׭o넏ι;/kt+(-EQ|>#〯׀3ʨ"2Ȉ"[tKoқ{H旴?$7=$_Nҫv}!qX@ҿT+pX)'ǁ fj)'ǁE@WN9=('ǁE@φ2qjFЫrrQ%QNG (r"QNhG0 7q(rrE993A('tA088`vtþy60MΟ?mꔤ`$'MHOX?w㴴e__P?q(r.RaAGݿROV6qs(Q\<]קL.2& n;5SR\zFEez #AT(r@%P"T(r@%P"T(r@%P"T(r@%P"T(r@%P"Tؾ4M[ÞvV[O;ZASwllb.\(rJJ) i1#muކG9'M*Z\]1=֮}' `PhGdnNRD`0(郲L`o^qfwvhWX Pλe < Mmllmc3M-WL-=k|a?kwl̳Hde骚s 0Q*/_zખGbGyJ]jʆ#|p-:R3\=bӡPp+v71> !)l^E ٳ?]qcY9ЦI)+rM9f2 8]}MJ!D,.7k' |90p5ZfՍLn<9?nk9Er(rtJMh9t߮1Q˺<)*(hn xޛ^U_?4 _.|7u% 'y[4C1~/ڲy>2~TC!Nzc!3Rݼp !&nߓ}NM:nJQmz@41 CH) ulϝiյ5Y(+kvF 57F#Fp )=fƣ?mt]oсϠ aο/D<DZXLkD)E"RjXl&,ߺEvj1ǎƎ>ی edE! xzwfwkJ初4#f'|]N̠0P+_,n;UwU2~@~}a$zP8H'Ew_>^mk9i) :=|< m灃Zl@Vkpe:tdj!D4B?2y@MOzjt,nټ}˔3uqמ~/)؛7~M;3-OTtڧ>i[]qJZ6ozܘq_}D9‘n s<˖ިYsfs?yxeiLFBj1_vhSkiמ[-W:mVs>~G»hۢ 0a̛9yŊ[N2$t?krYy.b7t~ό)F-^r_3wbqnռEKJ)a9 fZHO+-**tU'b.=(/Mga͞6,#'7\~vݷz4mVKS;~AS'ezСo]4v:ly93NIskj7!={Io/U f Ν;.]6_$RZ*Ok=;DڞGXRoH]+$Q{c .)@kfeW"`њ{ G;ٳ90"P\QR\E%ǁ"`$X7-[h ĢQ4VK˕1pԨѺF#E"K J TG(r@%P"T(r@%P"T(r@%P"T(rP@Jk6T?p$ESzmݩ×~n(gGwmzO~͛)}|Z\8@|?Mƪzb~kn#/'{ʤY9RM3gfT؏Om8&C9áNHM7x#҂q E#gNfEaBGO~u7-_px!QŪOm8&C7C0HMUj)Mmw8|:E@iB5MZ2R̞]S2&rG*¡ͪhyp| &K r;t !Zb[okmjHq9Le}ݕPEőFeZm]8j+<]L()3zk Ge|^zCk0ugmOϨkDBfL,%ro޹7|ffyY%>IW86o;6K EC7zɊ]mfgz~7߰؝_!މSgw 赛6䧘ɸiݰM7ք1[3[!hl :]+ovg<ڱSǗ-YJaӎ=k7n*)νU|{ה^Sݼ9U uJ+鉎ÉK2s欐Vjo8\GNy7}Ou{EkFٻܐ/8uzTNӓy_RZnْn }U͵@ź厢ܜh$跦d{5LoCqNzp7\7sŚ*}-Bt9~>iյpU{k7gU-%}jU5nZeuqS-=^ųg4ik4DWaN ݾnt-7Mh6omt r} s;cqהSӄ驎 5knq5o{\jwy3gWi Z!gkîO:Cj¢Dzj?(0P E0vDžk'>j O9iVqq(YәҾœb=LaӬvG풚.Jw,nÛ>ܕ9mrQr:߰Z-6j'BZ-90pf.'B;rtSNcv{K!LE=oz^fq;wx2I ֺr!>.e,4#?:f/L/**sp[fUmHimg<)N_A v2eev.`b-Y]P"4moEu]]v:ㆈDCVw" Yt݆#7t !&ۿ/_S0ÐOVR;u{N7D8f;4~n o k/}r BQ9,)uj eSwQؾ'a)Oqۭǎ5OZVq9R ߗ@ !Rk ]9-go/i4!݀nW7n30DB-Bp$I)j~PGܵI$4LFsk]O}ݬنnM"aLOǏtOv;5h瞏D<7?޳8;NOq޷SF4f==)Zao}i$,t {;@V;+w̶?EBB@0jεY& pogy=1f}G?{e3sGo۱}h,qJ '3TU.((4#/oCKrsnv8ǁ)wOC'*Z|BhJJ p}=Ivxv Nw8 !~q>ʺ+ZH9$H#\oZS%ӝeesv]vkƊ%K 0we¸q-۷~U1yƆ c!M۹|\5͸qv~wH̟rg;)yYvV]s6w_z)=omz8fv2 W7-^c޿ƌ[Q~")* bUنEǏ.*( v8ǁ>d  4+/g } %9lm{υ9Vu~~zkKlZQf+)U[DWV՝C&qO9Kkkk+/_?{\Փ#)/ ]6F|k5 ˊ ǔvܱtkF֠E"1v=мj;_W-(߱5j]7 s/E#şP=йwX4[Vq ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*YUI)kkk|x\,l6[VVvQQigF0\5ht.K,ЁjJT`9 |f͉D¡PP,t]8l׮)rRPaz\~헕KD0 # Yw*FH:)`bP5r@ұF}`P5r@ұF}`Gz_{'' 9Lr`t^#5Owƅ43hҒ|GrY?}|y6d--t_:^M-vOfYoĊ"uU4A 9 _j)/?:]ORFc-62juJ6fQsIKί@<>~w~_0KQ瑒"?0\AߙFf}m_xtuDny73yoGgF/|^_d2p௿y}ao] a4sX.꡿sF{X? қyr+_=8RGW}n{q!uǃos~Ϸf]N^sO3pƍ{bG._z~t$&Vmyk:fCK-{[o~羐WŬS_Ǘ^ޭyB ~wWOެ2dt{EQ?^^y{?COVWHģ!xԢEMRv:qq$W{xgkSѱ]+" O'>|ק&߾pkOMכ}go~}ueQ%~KCn=ihNl9j}mkB5[zܸ/IDAT}okl|b܉fŒ4766i >aRw!swVKmN'Pd_\ll gz՛߮ '] ]q _^&1>\;Ox}»XG*p *z͏o'۬Tlm L)S,QC !f_?{Z "R|(gZ<}ݟo~RJ!4M )4ΫB!b6o~t1$u]FkF/\tRJi-LK8n !baWVo 2#mfkKyN&Č43BX؝_p(r6] 7/HD)ۗ%;'>Sx皦hkck4>3/~.G|[?Ÿ}6ػBhg7Sٸgv}ߟ_ƿ|5B]7Lʌk35!P:=Cot=> ɩl6Sgw 0 4;=,;Np6xkNQ{݇N{MAqv]y~mZjZo f<25c}M/q0qv:/kEW?>f{/S=Ź#vLH$ѠjG+T\p^$Z9;bh;櫛͋X.kH[ؿ}? aO7㖇f iv*[bBwNۏ.Kl+ͽ{7ɇ4̱󍷲23ˬGzN{4s|><7n6É^O|uM=c^ş޷ D:߬.^I 7~ƞ]YV^~칪'FS^`Pv1qb g `yy:_ú=%--iBx4[=YUׄF,hXCC !tGZVǦ 7BtUHšf74%D"˧=c>Ӭ? 1t2_UBp%ZC"$]+pc|!)EH:9 L"^Qc@9 X#>PlDb0 ͦz `dɕ][[p8Uς,466(r@r|{ êgerƌWXH"$i3f̚1cA`U\(r@%P"T(r@%P"T(r@%PɪzNJY[[gefٲ5MS= 0Q䪭F/t\geWWWSTTz`5kN$geĉev}L"$W<7 4MՃk܏\"ta.kP5r@I)UkJ5rkJ5rkt"=weh&0 FH:kf+}uӵ6{U>ӝqi,S6kX11]QnjVw+EH:MXҕڻn+#ɥnt_Ǩ$ĥ,5R_~tmy_=_/ryȁ+EEm4*}֟ZuXK㲭_G}Q=%_ƸMOhze=[owWiBM{V j)fzo o?oET4i%4Z5ݚYz#O|kcռZ|(񊷞yPž=qѽ_~pI۞zDw|q-{[zIJY}}Wp}Co_-2gn[~^n]Ͽw}`N{sW߻5OsH韽p|5ݒ8^| k}e_4D<G-Zt`!eHi+X]γ_s7?y8mD˽co3_۵Q\(rFN⛮u$rnyJ捻CBaVoYsh_XO?!W[R3-a~'K`cc8{wݼBa~]dSN Tn7LeIBp---uG[k?wgi.?$FՇki/^x+;0q vլ,p m2pb]a6 rl!F ɩl67I74m,}~}wSwUl\*! ĴWΡ4ڃG )RR5o}&֊//}P6^zzMsB^pV=BHģA_/V'VD?}˿ iQs|7e@1^ӯk;/f fug=ov-Yf=mONRpwg3w|j0hmmmgϞz`x<ze' twVAh6:ݾay>oKTvOzzfфbRweexl1/s}!SYnE2mkm D !55/V cfxb ądf:5!4X$gG:ݑJÔ2IXƶ6Ьt]F, _|ae(^GF`vܱtY#$`}55]imox7PӱL[}VE|3ݏ)QLcmض>aZ[ LF`IG!&|OM{.|ɇYc~Y(r@Qɓ2_UBp%FtRh,FW%I9"$iR+t@(r@q9"$k\6-HX,CHbaTOl9 kkssYpEƆbՃ#EHbqϞ]pX,\.ט1 )rP4mƌY3fR= SED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*QJ9ED*Yss#_/2T\kP"TmsCIENDB`libhandy-0.0.13/doc/images/dialog-mobile.png000066400000000000000000000132201360136463700206200ustar00rootroot00000000000000PNG  IHDR~rLMݙ`AN}x6Iﱾj܈7kei~gw{P̈́^|+[|G)_|x~?5>͚{n?n]Co~BiEce_3d0#N6)t'Pzٰ'-Kz\|uLk,M)T/ܲSN_ lof)i߱"m\|m1bK?Յ%Bnckv3ozYnqlH?S7⫗=4g8xra>cvm\53aV=L6˥ ªU+7oS|4sJ)k )oRJT|@.-0/u_y]=_V8>6~2e-[5zAR{ӯV-__ޑS]_L[fҚB4eLJӁriUKm\[2vȦ;Wvȥ8 zF6K߻+Vlaih[7ʦbSJ^i3Nn=u^d㢉2͎8Ӵv¯_V槿qۿoM)=|eݯMl*WR_0oA>lmʚi85RJު֝=Qìz|6[c߾9s^]]V-wb#r;מͱ [<5:=`KI8x/w_vok=iM)8orEOX(XoXKOMY]H/s23{okgzן:_ⵞpl6}!UUvrO~o=L]w߹gʎ[ׯ5mRRJŵ/tuV|j7Z}??Rs)|-Cw7#(uU/0}f{bHwRJ7a:?(xQah7Rr{ᆱzǝކYꮗ M;W]rDGmYٽ 3;.9fʳg)\_hǚAѓH)aw;GI7C 8= ̪\ozWΐvnk驨h7ok[^2AzܑG}G{ >  VO|lZV驭MJϚ5) a3f. .[k&O~yˋ^H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z gT\w)o˲LJ ~ Ұ?8_Ϭ,zs.Ϫ?\sƥyO%vW~Mv?q| /1~d&=|l6}tX2}BJ)Mf:oSfOY<a|/% s׽9 {|˗^e۹~~+WYi.lʹxl57Խ>}1O[X1;uAf>|B߽վ_r-F_<7#r6N9KCMXъKG=_>G|~q#m?y\r#{x˘ws/9谞e:k}Jsl#X2}ڻT\:u}{)ng̺~~{E Fm=M6~YD{v;{B8냺7˥>fK8?TՓ;7dt9˃ڿ Ӟ}͜F}aѴݹqsX_|wƔ}t,no̒n?_x mWc'«4SZVͅp2OmwmT\:^L)TXdY]?l6 GKEŕ+VgXU+VSmY>/1LymcksS_/yAm>iѢ+R\buvm)iۮmzlƍ7i$=xҳdi޺Mͷe{l֭J{qwfɴl٢n&R*,[$5s=)L^}+2iB9~>)e*zZ+Oh3-[6PjóUpЯ\'pO#<ʺb*֭]4)}'w'u>0n'~}6B\qes\;{cSջy&;!>VԷgv4;яYR*xq;TyO<=Zݠ+|pwXR)oֶ.9bu:+6/&5xk6_xe!('4:枔v;[퓓&1S\C.]5oФU&?[͛'~ۍ̘1?iӅo;&{VWvN1M򖩱#ug7DO=U+@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@H@z  =@]L1qNe¿\w㮾 j"! =찮 S\wgkJ:wر8p0`3ANt ׾f-03V|{ `}^Dj'$ǧz7_XA\1q>cK.ūcjwb\ފ2A@ r%~7wW;܁;н 7YV.NSRH#16lX k[]>GXMVkvjR 02ܚd7 ƪe>R˖[ˀz{,"[L{Ҳrq 7^K/ҥ@um-&{i ;30.ں%lQ m=OznMK:![Diѐ`\ChδRXb;@idP\ШLlK gvEYpnkx) Ghnm 6ANO d;˼7hs(y#^ջC5GJ1ZӲ x{/M/d 1pٔcj\SCYyYx;k_A³;2+7 r06,R[%z>BEJL>-{ tÚU et(ncqH0b>hE0ڐ9fZ۹x+ph As~MfNX96<ڴ#E*yw8[gҼFHԲhИ~AP\TCo@XmBgk?DS?0+~xX `d0$.8(ͯOZYNH(W*Ә=q"uVz2.4Xʶh~]m4*ej̎3QwMg:9 \OE|CѦJq/1 $@u pri砸.sq@8tK#|+7rjAak7sN$1rgS.tLD)Q gbDr 2Q+yZ-#vZ= asuE:jCNʵ97mdbU=s'K~ZٕoFj^PB}޸:ω/ͪ4"HK=.PB*^ .WM"c'jOܫzo%ehϴ;C8pA8:ǸqK~2Hd?8jz$|yFp?iv-jDW/[tM;؎JPZOSFڒ=q~`p(Lxu A#rR{SZ#Ol0DDYk_yW}Z -tq2GjGpz5N58Ņ$n ڤI-Gꇋ=lMCd]J5RcGG7w)VqΒAgO_Ԅ7#V `ߩkܿ\b8TBiCCPICC profilex}=H@߶8A!Cu"*U(BP+`r4iHR\ׂ?Ug]\AIEJ.)ޗTkP5H'B6*^ 4$fswq]Q&|, xxz9GYIRωG #e8̨IGbJJZ$V,MR !2XЪ"}B>aNRz!BH9ӗֽT4{^,M.r%`6Qu !BJLM06jhI vɠ fG!!BQͼL.W9*YЊf@kbH)$BHH!LߊeI+G$HII>@1$BH !$g=-&mF׉N/HfCBh&o^CB!д8ˣ$KaC#qE UBhUvLD\3R !2ѼUG%H$$r*xfV0u aE="hbH!.% rm3L\PͤL̈́NA|_za(F B!EH $0ve9.I$9PecfJ6/ǩ*z. zSH>)B)G9J(db.zu<8z.z=" brˢL!A&ˠʲC!D׺#1LH39Lu,-I*iu=>v0Q.C]ȄB)e)L2B$ 0&KLe^LuI)}^HƄPHiu^ ZFH)$BH)KancI \.'*pְa,P%͛dY.q1TLsL@R}S[c"iA4`f`̙PU$B!zh4 ׋}1|px&HF5n" fRhBDP~D]F#l#Bɀ$IPU\n7ܞ!8z݁PR({eȲ21E2\&XRhԍ:$m`ˡbq8!taB!\QQS3 'NpDCD9r8I+Ih0Ғ4ƭHZv&]VMB!>"4m3f,o߉1c1(tBV-&i򙭥и8Ych4 Mpi;Xr\BW W>ed45(UU[ .FȩRhtH3c u~|hAzK1#BPҙ$Z"!qqSvֆ*HM[ gKl)IY.M! wiьo, !݅hdN<$ g. DXCuwubQ|P3k`8Dp!CU|vn[ a)RL4]R|דXOKC6w[ƬKY5!yq4A ֡ Xᾳkf9iwpE:2W*7U>-A$˜P]3 %Z4 v8dY:L dž`a j]pV$n3]BU;rCM Z /F=Zg@#,}J.!tHBh(XVTbHe%wENJ>c# !cꬹp8ɨ$ahe͚{ކxҒF ˅MESܨڙ V::^9so B]SkbmW{zC!u].$HBil4$allYq0qu^Qޅmeg϶`cfXF$ ԡ1RQaN|tR[ H BĂm%Ɣ?U,gr̷$Y.BN2)vB#As,$ILJmF4AeU5P UՈfcr3˧.!CtCNt:{OuB̟FWǑejӻY\KW%7)4G%1S.zS}R`;i>Sg!V_]Ƅ$j+dtGP^*ajK9N8Ip.9Z %Zlv8ج8[K!)˺\Q(!Og\-_^\B63RƲJE/0XC]\9ePքb<}$e&J[Tr>6FNYAvjc=$Cs\s5wAԞܸL6Ʉ50(οVxBH7IJ_<6˯;{,zÌ%*Ib(֚y}=M/]W.^G1OuXF%IjnKpTs[;ظE1#cچ}/2BBJnogϚ0a|]'$IzB/ۍ !̈wɣ [Bsӧ߿1PGa?V &_Ss:(`u @ɘƕ.eͺKmoCQ]]]y\3[MQիҒ|&>tMK^!^ݸg4}*#6BA'[><][^.̊4p ط w)H;[Ǐ~!uW6 !K:t(qo@Ӣsuv&u'+:]|t>?*4<W `ȑX2 Zηwʚ[b|S P}5X$-@p\_,"y.OBc 2+yuBP:~~]P;6mW\w/O[1jQ^8Ls8yV - rvڱsXm N5'{2; 8㟄;^ ,o(BɁv;. ?kX5 </sכ{ŝ,ʼnqcmr\J<&Æ ӧfz,?vҞ2y2`˶iCamwZKMnRjwo]ty&+bq>%-6,}8.\.Ex}JgIQP1 amƴi Zڰrj\83Ԅ-w#~>KM͐;}ƯDŽ[X秹H4G{Ǽ-#ƎgՍVfE}|1L</D1fKqnƢ֧/IWAh5k/][Me̞9g,h>Zq}!!Jh 1--ۯ%~;`>oA6Bp~BJ1_7g@sZܹs`cRRzQ&hR'XG !;#矇VBs^4tw^6vm`sm X !=F"D"bG)d(r4D< !eRE@!B},ItMܯP]2IhrOM {C{<BH)P*q)ؙ&EQ{v Itvz((_Vg0YQ VΊGqdbZ ?1r)9)4{'N'?M2iQlzN)R:]8umPTaYNbLWY!q^>9U2&Wmevv| kNT fpʠv4oSաڲܬSY(*5 ׋C oCQ.|ΟE/`[ a !V`;B􄈭 O:$ٳiyݢ/kۀW$( ei*7$ ,Cu kH$ Q_,pbPIb2erU'.kGBvDʷ=8B8J0>^L*W9DT3P&\n8.!*,;zlUUaQ\I+U|iN+˟]6))$Gb`$(0d :1i.!qՎBH)$6@ !qqRh^7$,V]H ՁBB~X ! _Rp;B!RB%BR(˙荄C=g Jmp8],B!DŽB!RH!B(B!RH!B(B!RH!B(B!RH!B(B!RH!B(ĄShim{ZB!P 7c,X/[mǂKpnAtu 0|a%xL{g<`]tw]BPc͖~n=,^6 g/_||tp\8|(ZZ0bYw6-\U +yگ YTٲm;*+b)pO4|#X !P IvN'|$)3Ϣ:5-z olي^L8u1m*__M|ʡC0qnWQ#SO6]..w^:؁+.Ɩδ$_#p6 2;97|躎[n=?ituuaK߅{[ ‡OCMu5la7c44ۏ@ }\.W\ !T]|募ǗxIENDB`libhandy-0.0.13/doc/images/keypad.png000066400000000000000000001022601360136463700173740ustar00rootroot00000000000000PNG  IHDRh("zsBIT|dtEXtSoftwaregnome-screenshot>.tEXtCreation TimeMon 07 Oct 2019 05:21:51 PM CEST.ʹ IDATxwT9Ӷ.W {{DKb՘XbMܘM, ( m[f[X`0g|5P_ED(|a͓\_;w\oח9{KsbssS|0^p=ܳ̽7حE/ xa6- 1۶ZAX}he0 ۷|>0 ܶuB`[6-Mf/Zh&`0 ,xYD۶qmc6@~PŶm#" X 5[a_^w"0:.!"})Y'?B /""hw8S0'mٝrAVH_q ""m '&"r!#Grݟ`kq  { ⾯}R2cL-AѪsAr>qDD/[!555_;iGOMRk`}TTTJ=8ZU̓!$;+, ?hUu[mm!_P_jqHG~Naq1YY<#<#dgeQT\Ïg^Ymۄ|TTVǠ袋.c<#؁@ O> `^~ؿ{MΫ8k8t0 |))ߝ8w 2:V8D$R3(;l]a\ HHRRףmp_=8g),.>z6URx`Cm;z`mϿa|#]DRtFmۦj 44gP985[ZD.vQRDD.q'8ɵWqA4Cۦ=H*MS}QѡMS}=RYDضȐ͢-ˢǃGPRH>1 5Mtݹ bbq\`0H]M5 NYD.{Іi  Pq-[me&^(lr9't a_wm700M$`%̠aueYDnٵam;Hx&'Ԡp}&3c0:f++;Ş8)O7ڙf""f'"PₓBP_!"P hR@8ZDġ""q(C)EDJ-"P hR@8ZDġ""q(C)EDJ-"P hR@8ZDġ""q(C)EDJ-"P hR@8ZDġ""q(CC]C(I$'3x`..Kzu\~F l=U·idee2iRB] hR0dض};G#> \.WS@IZ~^[:E%%.咖 zB]8ql+AQI o.KP[ h9ή{ط@3g=.CJ-lfͺu.Ynf%t+(>e9u q t(u }V~{9Z:.+ u }{Z:ֆ>.%)CKsKK賚C]8Z:Pg,=r2C)EDJ-"P:>wy_>sI/r4q(C)EDJ-"P hR@8ZDġ""q(C)EDJ-"P hR@8ZDġ:;E3|ơu磖Kf""q(C)EDJ-"P hR@8;ȥ|nvo_li-"P hR@8ZDġ""q(C)EDJ-"P hR@8ZDġ""q(C uvgC|G-͠EDJ-"P hR@8ZDġ""q(w K=:> ZDġ""q(C)EDJ-"P hR@8ZDġ""q(C)EDJ-"P h2P!ΠZ.=A8ZDġ""n,āP 1ѡ.Ju @ h鐔P uYYlh{v:o040'ei-33 u}NVV&zd h`S.%Q[_MLt49Yide0]R?#Fr6o`cCŠacOζmm>} (=TƎ{92r#;\:K Hz~K0cŠA'mWz[w v\_Ȟ= c>jܺ[`wMUe~iwr8~߶-@ PhS}l5<:kouq :Z-^O7pq,6l(oOk v}NQ|ᅢiw~i0غ'?.Oș ZN`ˮuadɬfc=eM}uWcn@l3gL|2fB]  >ܼfCin8LlbZ~(=\@,lW7OeR6t81em4T~6yznOYlߏRRRv񢳯IGˈ@} t<^!x#8RVɶi;Y h9Ʀf:ϓ(#)"2ضg/~e5Jb㓱i0bpeGǰw&oj)ߍim0&1cYp . 20gpҜ̞=zʼn dddP]@u} %$g /XpX1$'%ˇkưܔ*bP[@:qӸb,h[HNN.Z*~Zr 6F%6>5Ɋߧ[YAqqAױ>u}촔4l6&u$<Q[ayb\$1%*,y.FױolCK8UæsiX_z Z[/%%% `I.:2HW4Saִגa77Pd{+{ Ek]Uw|]Ux;2Ҷ A߹GkhG hR\q9DlXR[W6 WP@uU%MM455܄:_nW۸9q}thNZNk@\8/w\ Uu:NmrVO.ð X a3$7`Z6墸8qUT \Arsoo= g nض߰;m?z]'ACӰѶ`- t3 9پ#G6(*joo u3kO-mH7x#Nkk.gaMU[{1@" pWz]}uZ &=5ch[._NX|j6rsZ-sjZ_ӎr`U-~h֎p$5%,c}p/qzY0 T]ۼ|ErVRb;nʱ@AQnY)lF M˖IUőNFAKɽ޸0?&mlk+䤦7Α^^;>yRvl<ضYaظ;>|6cX6.b+;Hx͈A\~%{htU;jqY8j0%)+nYxg"".ӵSƏ%Nlp嘆a3e`d+'_e衜frN]LzW,XK\WV2M`3(3صw=ZAss3nIdx11d8"$--;EhwgXv)9hV*@lTY>omH/-;vQ\GuM5^I~HIgT6QTav\wՕdTV $;+#FfԖ:G\"Nm3Xa;~iq˜Yft2e:7Jjq&yy:;p(rS@i\'rQ@ٿC#iv hq E;! hqozPV&)ȥF-d&{qlL7o2; ZDġ""q(C)EDJ-"P hR@8ZDġ""q(C)EDJ-"P hR@!A_|ӳXM .8mogkGOi2ԥH)т'%.GDNCg=pIp ?q2w 0),UooIZKΐP]UIp^~ Zn 82dP|+^?ËK0$-E%TW2 .iw{vPCFrӮ'p/OuEM͙I܀"}L7$2*~9eAd3rp.㉉ +D8ywS\qd|DE0v{ 0`IMm.Vۄ+ 0=Y)4᥼ 5GIی6DEq O=#INI#2"sM6ngƔ)m=)/#hKZ}@ sehԤT|Sv Ǎ OmW¼^, |am lNN4VUaB%:ecepLU?]˞:lwݛ(E䴚6=FG|j6 l|ML˳O$*GB]cGk ֭nŠ_B*Gzmc$yy1+7 =,ճhk+zət8MJ=<} v.{dz=)խЍXEŀR1ig)]9h5;>f_z29G=)=m#|D\az0_Z0eڮ/]ǢWgdˁl^'2B]yja}C5T4ӯgn=-n328T?鵳޷̮z){yZKJ!{%r """c/r5۶iadL2 I<4''%wnm[AVš?+G;xdU݁R\949L!^kxeQ6,Yq 2گ'w'['q/ O\pflf6}2MO;(U_ɂ33m{yt`}|yH7q;LJld )g?n@ɹ$DG02=JݓHҢW4,G{ytp>݇cr. X2kTqqi퇬RjCԟֱk_||XZsb3FGM}3.l Ā֯/%+!@׶8z]@c3#sO m|Oۨ>0?ɝe4*Wv+<O?I#e,ϸjbfۼj? sWb_e;[*5$ɜWs-/~ b > ~ey'l7?=)L,Q"~h&j/ݭ0"㉏bؽmx.9~35; 斫X|7Aw޵}"n2+K{Y &9a?۸HNN999 y5Җ3ߺ[2~q/,줧Yl/=-[?g ?soMƌ3蘬FbFX6^cۘc/D;I"`d{vt.E;;fi_tm,n[kmw͢{g@3I.3qzEvBS-;7 > knSpt9#}e|;TdE,~p<8x`륨CfNF$9ɸ-p9K瀞xp8w)y8o/=Wbo?_8=ˁ @4Ρf>W \~|n<\3xϦvм^V,=t8\݌};T+!ĤL,\38!tqkE2f\6. ͹2 k1bݛ,^Y wakKK=>KO罯}WʏU?u~#}NUϾU5f-dNRlzWN=}og#wj +6Gsn# MޑFnWGHumWMJ6ʧ`'|G{=2SKwy _8e^w/_&\ns 0p]J~6.Fxcq7p'oQ R_Z#e;Q_|O7\eP5+m_ WF6iGRPV](jk*I`E]kyY=VǟUO~Rqx;|a).ٶO_\P 悍bkAV~S{,Sś~[`||f<ݓyfwȈcKy"e>/ab؈纛'RZ˵3Zs 9qUNfndOVI~T23蚯܆ia?F5ӹfKظy#|K64FE7-3׈/w[0b >JsftyR[<1a\x- >Q\}ѷٕì+kxds+[?w3?JNW\HFcXf@s `ls: 0zh][72a[9G:e m@K_4+w;ml438ǧ&i=7>wR^0MCzU 3G:o-Eޫ{MTN{S]yپa./h_|F?=|i 69+gϠ>^6&.Gb|t@|zlѝa8\Xvm=K hqo1"#fu̜{cH+ޗGi~=l~m1aGˊ{m&jGj|{ Z>~в_RY`nG5웬}/҇yKH҃=E|1lɯ~YϜk|[ٚ_wo 5=ZDPjk2QE}uϦ 00b)koueU mkxzQ.㇦rh76WwpX£bp=z`#jÃ؁T7q͘=ĵ-j*Y8 &qIr:fС^šszO Ϯ,arjinc-B ` F~.x/L a&imz~Pt;a8!1Lcu)[EKK MMM477[rv|u,=)E1:30 |':ΠȮ(EđN CR )EW |"q(C)EDJ-"P hR@8ZDġ""q}4TB)))ڵ)!"P hR@8ZDġ""q(C)EDJ-"P hR@8T!"۶0UrP@9'suCZ}"rVضmPq>uX 0L rv1MWG@oPvKBb2QwJ-"g̶mlHi%Ey475LF@#HSce)?rKlxL0 i۶[_ Akk9Z^%=3~{:ZDN}ٽ} 5G8.6"2('2h(J ߿~Xp.Am.ۍ&Itt,CF`K^>knc5ضSguFkY\6u@#XVceaHcC;mr:ZDN+MDcpͷfˉALٹ۲zxZDe6e{V<0 u^c04l4nƺZ,Ytg1A{v踡.u9u9u]c6%EOLn#>1Z<=ICy(;r^нa[AVš?+G;xdUG?8WM"v@o~W:^YD;Fu KtVIɻvlZ/Fp͝W $!:{IZ*Mc?p X훙3A\u kH؇hf<qk絶c0?Νlm_ɂ33m R19h_ƼRJ۶i|b6NHcckEz¨@_x.~8^4 ]x R7>{bY@-om6wʗ]k~=柸sw17_e|:[38l5]07!mVm+ٱ:f¹5V2X,}m7SoGq>F_|'>[DA-F|m{ϛ, >W>D}oodwFc=˧ _xl-c)z m/N0>Pe z^Ż\73H{36z˲HHM}Yx{Wɧ Ǥ2xgZ[~j51navm~{5ɟ+Y/ز-` |kw61N={!sXIf^kۯy tAUO=Nrdv0ڕ䧿xX8OfGڨ̙ T۲x6 e ۽wɓ?\,-7frݏˆ'>OKM`F|ԱC]F̚bgsUXU;Z̾u7^X,N:XvڱOSIgpN#o,Deym;~L`2il|g[1o<O3q'C @lؖ ;C^,3_qJ8V="{}>@ MlÃk1嚛];Gbf_%+YQ~- .byڱOSIp5K𑿚mAA?3,XAg}W|v3} Y 4M[/--\n ӧwtb0=>NUϾU_ ݛu>-ihlhP-hqzT' ]wQh h o?VaW>;yO_s8{o' z'_3s_"}XLš/B{5r <{a>2VǶ1u4qS #rs7So շ_K}|g0b~1nIw{ku7geK8sDwZHHHq/&wnȄ)/n{G:e Nj@ )fx;w/Tl_~&vnYObJI)=%.%">d=ψqykV1|̄ltd+|s/afcz0 ^/q I'S@Oax== g7lcy$8U~F%~S\{HioqArJ:Q1۹s,;=n_=mj#&_* ^ %xnң?{YÅKKfM?ſ<E.4|$o!Zl6yv  ۣg۶ie8xx*A# H IDATHg`C%.0p(ܮ[4 ػ# K\l,iXٶ-jp.{F{К,HZBoKLJ4]T*bH&!9j6e)*#*:mۻ8+㟙{7TARn11jzbh1ES5c$."(wvc.p-wܻ׋;sÜ>̙ԔnK44P_KrZ? GJB0 bbbߟظ8K)-)X!edE\Bdzx\.*+ʩ*?I\|)i( (R^MlL<cΧ-8ӯKOO'11+ʨ,d)ZY0L 4ָ>̺ A4pNR2윈!{$A !H7!!8pxws88N∏o>XE+IBkYA||B-6% Z!lJBؔ$h!)IBaS¦$A !MIB-6% Z!lWn4OeuVo4A!ڔ`xn$aZ;z~zMEشBKkp?n no`|Alo4A!ڴ˽TtW>k}qXCŽ&$B-6% Z!lʶ zS{ Bѫl-6% Z!lJBؔ$h!)IBaS¦$A !MIB-6% Z!lJBؔ$h!)IBaS¦$A !MIB-6eeݚnB*&h!$A !MIB-6+ :;AVEؼB"/YSgoltxq+Y!hS 9_08W+mS?8荭 !\08[J { uW[ "(9I(6% Z!lJBؔ$h!)IBaS¦$A !MIB-6ջ1FLŗ'q@'k":ֺߝ|$.3$A@k֚j*NvP*:gI&9%x'q% *r /3:BjTWUrnW=Y9@NEtͮ +*J9gDXʂ0p&ag璜amv:ĥ3qY]C,?叞5eaY~r QY^֡RK[I\DEF)@Bb"JnRhIHLG)n(q EtTН,T8 R* %#q# :D}>J\oHHB}B8۾K\:Z$a NEt$Iw NEt q!MIB!/‹{';BU<Ǐvzm5ۺ#T{bH-`?+]ाTx)KWgבb9^ZCA^.1OĎC]o?})kU ӡ{f]v>i '}/[!$ :DJ~7oTr69𸽭ivy̞;WUR1B) u7q'xz7n !kj.g~=2t8?^r4JiRh suq71;28f?tjSF|q_;^y;\9,ۃyz\w*J{{Ilwxx|>Mon|(EϴKr7x29SSdx#HU8me[}n*}τL>qv(1m-//dcFΛMa*o<n$M/};.od@tz<{_EEXM73/ 6R[ GCNRSwq>rJD#)yk5ydt.旙lvϰ6l;<{ ߺ6d9/+lC|<{oxpq#AOgUQx;]|WPrx+7AGVTm}"l΅;m.qgֺK }D_X^u Xf<9z%߼1rA܈ݯ}"-"*烿W4AL\~0<&T~>sngK]IT ۶lӧE-"wyoSr8_J{㠾F[~ҳiq;} Q%݃{)_Qtc$w33w]l$qy??m\ZP1 a#H$h[@Y;><4 o88|-߄5_!ٖn]o ̟sG)N$Pu}C7EiGS`"6*&N@;5ڄ \5KmǙdNy4{P|$1zž$A [;YQGvfޜh@i]* 5UĤo(E\B1Ħi&NÈqz>fާ&K5%$A 3{# nK6 Šs8td?#ޥYl9Eňs/Ϊyy$ጁKgܻՍ]ז4|d3[X{:5J),:bTo7GܷQp8MOhI"qi8BaS2-NEtT!0&JnF)eav a:HJI!)i8a$.EOttJ)Rg#L}˯ibf;%8 fE/0ehlI!hY9D\זҕ=/q% :D-@G3}]IH$.I\D{dBؔT(O]%8h$*/Ѵ]>#q N":Cts[j,;BEɅ >!lSRw:KpY(J"`i2p8#OuU%NjvՓ $.I\DWȕ]\ TQVZ9c&6^%ya0M dώ-d%95C4K\ZH*EZk*?cYl[0J\ZM":/:_sZkQJ[)J\/+q]!t'< ,'>0ڹEБpue%.ݷN S$A !MI\!m%.-}r0Tr'q N":At'q N":C8¦$A !MGR''_=++/xp ǟ2ܰPg\x:t 6U"[xhv(NNNrsrެ̟~Rq6Cã%?>lq5abnt3xNi-]xGyM-O *K8g+'FzCڧWKo8bamiGXڢqܝ먏K[?o+Pn$wdm$b{?轕C0*$ANJ(<k3jʪNe>Q@or{2C&ǔmZY&{NOfI󾚩Ǘ%6@i sk~<݇?#A(."C7ա~4/> }W#t?vJay;7g4f||qT >B)Rߏ $ALw~VAm?hnp0∏}w\t,_[3 n$ KFy? #v(컥=G|,(Ot<گK|wyƆI4Z$IL$1PX繇Nڈ3?#(Sh7 n};xkS#={ ?+d\ 'v{\?9u؈圑1Y j6ĭcIs3O3t?9A$Aۘgs{qk[%O֭~oME!*c,~}'|`z=GQS]}ĩJj ~Xvp*fݬ{]ƯuJEGG6^OƏ>(flÍӱ֨COx *Y#%/c65&."8CͶxF,?nh*lvaheCNv6ɚRN{_Օ7n[[s\5훜gюKw><^!pWo nUpp[<ĭye|v5Jf[_cC [^9WĽ'拵HeEu8F &=^c _ '^~8lrX+ؠ}0IHJh{/w_8 t g|'xve]Wv&tyE5w3|i3g]:_29 0 Cz7,^|35Z)g~ZOޭml:~q~4C;}< MBQʋʚ˭ņEuOX.iMWX|߹~5ZƋ,-RQЖIwhMr Ina?{l>.+MvnN;[m7y*RH?Z~Έ#;'s8cL\`իvIkQ^VqjƇIv;6!5yg 2` rc9YȚ-F<^;R@ۚ$hѪnx*JFNl9&cog_MlHyy}f/ ЊY$u—; PkgmF\*ȊkS0~]q Cr%FF,y_~{C0Ft Ed00ǘ#~-ٰRVMN#'8c'\+Aq=^zG+y_|g+-g9YIGjf.#&Lױ`R:7n$Ar3.P\ }YX=ٜLځa8;09kyh,i|rÁr"6~Wۗ {9^W-vMAƸh*'|x= ъA !MI*%8¦DI\ Z!lJ*I=%8 ¦DI\N :d{a,Z-湗^đ}RNy߼2|FGM'.e/l{[aCRN4T{u'pZ>,>˱[WpG׳7xXjӋqHm+&DvkC^aD3ђjUs`Q}[? 7s9Ml޵e5/Ɲ8bZ?1Wk+W2&?]ka˘}t/rHm#u.EU>Ə?:[ukaų .-9Z?j^("5#y9.`˶ug ?l||rʋn0\?}&%%Eγ]Nj{4|P./\/p+;woTL<6d+03c tb(8r+f;vo;b\Zho/OMeˡRN=8".0 /ϒw3`4[ӿs !ҢO[  IDATJQI eAyq2N\DPm$;=GR.[oQD1"5)U3r^Z&&ɈcβfxNoAOLr>wln: =HV"!_\t q؈a(\477p~OԷjj&ivI\iv]8[vhp֑&qi{ΑC4um/]9#q9QC th98Cוݍ NEG !MIݍ"},tUI\HQRMk>$.3$A 0ʲ].T\x`I&9%x'q% *r /3:BjTWUrnW=Y9@NEt\I͕PMeŜ3fbUbpFaɞ[KrjZ.i&q$U2T1 Dz'`e+Et^t|a̹ $$&F#$$&qգjR_V"B*Njy^YO|a`ss%#q# :D}>J\oHHB}B8۾K\:Z$a NEt$Iw NEt q!MIB!jx,Gx:bE,>o9_ʲ5{8xGPc1 /Ak;6ngSGXbŽC%TK͠q|j\3oJQCQE_lLLیrO_h}M%ܵ“oƴL;}Q W\,J<{-Vxʊ)+f߆yY|ws]a_|v:r#O9RI x)O߾˽?}qԬ;wy6OxÑ<_?|1 ImCC]{}}Xٸy]9?'XUsKnkŸtQO>L2j~{YQ{g;O?ަh$2қWsp<ߕt5DUG"_J4[#Q,8?tZo&7ΰMv7bM` ~G~{34FF&GpWSb.;KÚ,9tSߛHPL~w+gjb:TE '܁g5n,RMw|dͼq-Jw?{lp8b>}^b o |Ο=Jt9Əu_DCm8nӠblOsr&y\*)d 'q7 GGyyͩd*ii#\S;($԰a1Iڅ37';54]pUӢB敿~?Jn4=tg0U±61dfbHp扐tz:/r}=ʚs$k-hQȍ|*hEoZ+0#U@= 3c W( 7?&XZ4ZPT{NJڪVAR~|E:I:6Aܘkvg~=菸_;QMCXGYXȈcЬ/Iii}0GdӸ;n'+8mqzȁ$Ago7.V.e [Lڣ8~a>||+Rc}$珦 p1:8m)qVu[7o4(|>o;v)6:t#UNV{e g'm>^|m0I'+hmωM/^ewL3nf6dFx/ߘڢ&3IISD1,uS7b}Uu??1{1狿/= ߺ\ȇTDu*q"w>$U\tPrYgx[E>H_;!PTwo% 47>w{4+ɓ.T13$w7]E_/s3e Q ~nf444 4_]UF0".m!+ڧ)o7O5pu\҇zc ̨3'rgyM]{?~v/'kWzn~KQa\cƤEAt$PGx \wXbP~FQSF@:!D, jNt+[}f 5^`WhDwo^̢}-$Nڶqbhm\Ǵ;/ *PUA .G:-\-PG˪`>ӻP諹f)u9Iv+Y]/3s݅6tO#m劦Ty|>s ܟy*9_Ɯ}DsC_ᣣ=e@N Ta߾}_Fr]2PEomsr^]\8 NԂ=/s猭+͋yt61G|<߷a+֜\yE>n JIUX{ͯ L~>#ki G_q6b_p_ye=\&IfrMݗWF I6+Wʻ.Lɺ\JׂPU6O\rwqx1ťUͧ`c^F쒜$n%,co?UI&_Lfƅ2sHSnIDt9a5w7jSq01 4@kR(P~~_0˃1+8&i`gNRhea6l$A(RpEf gyK8aNEQ^C̃B :T1 $.I\D'H-6%tȤ$ NEtTBaSRAH$.I\DgH-6%t$ NEt$uY*'{))&-ϯhFJZ~rR6e:VYӰ}E$mdNcb33sLɊk-+=$&_[o W]2LM]0`ȉ\1U_@wn.˒M[0tl1S_ҕ+Ӯ@ie̾ti9y6|ŗ^UTA&~pDlfK?u>> @ǁhF ƾ+)1XК9lPEdg1ӮaCعu%nL4 HKnњC+ʙ>c.y-@%ķzaQI e=yN eqfLg͞">SZ6-Iv( js#1!'h̸ JOㇲ ƩHKk6hXWiɔj w D@S?5342YCOΠ,<~9|(C$ bv,~A~Z"UnŇ0ҚǝK_8YfU Ͱl >}L>1MhX)MKhʦ{W[:i/غ擝CrYg`=$!ŅOepS6fel h] Cqɧ/'vMhƾ6ZkRX'3tĨ۶4(?gLm34'vf\z%":F*0 0{>9 !z$.hY *g$.&q!' C`:L[᧔ivP'q%Q A||nFعLKpY;)0 3(/H)EEyt t<.c$AwaIBR2))۵ ,+'rbYՕݹĔ=/q N"Ju5eaYՕKEGsRRGJZ:Áamv:ĥ3qE-;R TH?Si&q N"BuQ LoE:ZG:ĥu@":GtZV4׵tDK\DGIQ2Ѣ e?$.g_W":Bt748Kpf'6%t7uW'q N"# :DJEz t4uOH\ΐֺj*NvPQri:OH$=+vI\ΒEVVZL]MCH뗁!~*9^tHu Kkr%a4WB5UsΘW)J] ib&{vl!.ɩiYҚEtW,.ZSY^ƀ!XGOghw܂ATuhTjy3v7շZURJQ|Y ;Yxe>q}$.Et$+(qu"M_Ǟ" Z!lJt l.qkѮ/{w08Kp C$-8Kp2!6% Z!lJ8"b=^Ǭ{_{USƷdɊ5l=|9 >K^3SOQG_̫M"&.6KΛ?kI! 稢 D #ó1C ״mkޛpEӰg1{}j+G:qm n'|v-> mio;E% ڮ1++b7o;Mm8vmS( Ks|퇯qȯA[]/_3[ۣⲽbV]mWq\ wX" '\\12f"緢é#t;4C7sӋߴɓ >JPe;/]z/ixmq}s-8/QVDp YX> e1iY8Rvh[ *vUj>3}-%ﲦM}LRw;XLF]8O_c2\:̓&''+cD&eHKA",:@q |F%cFV,4BtU,v.G|h#Ao/Wn㯻AǓ?jwc7<񋙍ۇvdîbo̡lӿg|r?u0U?ǟ c|e$qQ՝mFi)g-TҝG]O]Cfvq8muoS4 -=smbXǃ۫m12ړmho4XFOt˜u  983Uc^f53Tl{A}suyY-^M 95j+9H5ͩ3'x['bSwQPD3H$%'aPn҅&&,@uu)f )2'"$PX$9#D4Эm3-i5j?Gҩ`1ldhi۠J-Rz$Iu0O]kO a Ρ$AG'1H>|s-,E- Rl9qks( mٺ=g\}¦VԨʃll_H4CODs:\x'Ȓ>._@2byTD<_v*=O1y y}4_fʤS ƵyQF$$'-l1ˍUyK6̓6ɺa-Aw>}YA+/Ol+Ic_ȫ'gpx>y{so N*|/u#*2swS^\ަ7NC}]m݀ZJy+抉K& :d _\OubFPχ C퉕|;#wIDAT5i1HGVp{7>c~I!\~]pá`1h!)C%%tp$.B :dR'q N":N*h!)C$Pp$.3B :TR'q N":AtȤ'q N":N8DT}Q3"I1ywRy.:¦d :D:5lڸ7ZNJB=x! :T2\%/+9|\vew]%lɞeBI"8Ff$At@~@QGBؔ$85㶯] r%kRAt(zaivP'q%Q A||nFعOH$.$AwR+agPQ^UR2R32[$.x\DH00MdSRطkՕXM6eQ]Yޝ[ILI!114kI"h[g-5eaYՕKEGsRRGJZ:Áamv:ĥ3qE-;R TH?Si&q N"BuQs8٪E@ tt6KEt$ik}ʉחdE~H\ξEt$n$hp$.=2N!lJBؔ$h!)IBaS¦$A !MIB-6% Z!lJBؔ$h!)IBaS¦$A !MIB-6% Z!lJBؔ$h!)IBaS¦$A !MIB-6% Z!lJBؔ$h!)IBaS¦$A !MIB-6% Z!lJBؔ$h!)IBaS¦$A !MIB-6% Z!lJBؔ$h!)IBaS¦$A !M.r~~IENDB`libhandy-0.0.13/doc/images/list.png000066400000000000000000001042441360136463700170760ustar00rootroot00000000000000PNG  IHDR@KsBITOtEXtSoftwaremate-screenshotȖJ IDATxy\r' ^YiC˴-+vhZiiWx  %ͲX%PTY}==3{fg?;lݺ@D4dȃ-][DTUUҕ5Ruܷ"#T[z c@D"P2ԾW .]1LM޷\.ww`m i(_t{^Jҟ9sҥKq[tP%`+oe۴ IHC(X$M&q<7GfcbF$Y9 "7) pH@T0RD#eirqI3ocq{XӏKJz罌̶m` pK8KJK~w~Fff+?O?| ͧOzws۩cwrr"Ҳy7>d۔DNhdg "=u@@/ϜURRRZZ:}k '&)Ι_.a|Ç֫]q狞{ i_9a i^_.IolDfYp7j;=j.B iK|$7HeXDBD<ϗk4&( @A rL&ciKhM3Rh_.u@@Ff+**nfDW^MҌEEMxӌ9cY3}ki/yow:;kB.?.{0Ϭo^|9/蹗_tioݲ;o <[?̝eЀ$ ^ JV.MppJjj4~rvy+g [ +dQTk~a&鷉?I-G?iHJq@(=.- ]+@x{6Z$` bvtKxu{BD{gՏB(B_Aqyjڅ 0$YsvϾtj?FhVe-L Ncz h4Eť3<\ ~H`niʣe$"s#e%A) Mcd*)+/!XW& %\Rb[BZz֞rj `4VO{omZGrpq1Iwxi1%%Vow'4+2{Rv+JRT^kfnBlI3'#" '>JSaG{.ﭦg35 pplI=ݬMY]սRuݨo&b6Cϰ:tܠ)8ixٽGSY/l/(sy}NA~խĝP:|PںEFDzm;_{$1'{tjk2=Ggv>KRQTsSvF/W?zLSiru΂T_O:slԠJ—A)/^.-7ڷGu.M0ɺ>0jNyR{ He0`REUiƟM붻H aeʸ3)}h63;}23lkޖJ2pTa_bH:_"U ]G D|#l& &&\BdD^Az9lǰDtFmq .T"W'p:ix%b;9HK+^Y'UDJDF3.9)KpNd\8w_=uSkJNMz't}:E".s:5soYȡ]}:u.Tn'gw,((QITܼlW 픕lNn@$aX^b$.κ:~Bk?{4*vWr ~;%yz9CUEA$;S7F"UI:$0AjgWm˪ . !S,uiZb⼺۶m/SVr̼xmkt6bЁlMYyE`}xbκ6m-I8 tNt[TRIJ-v)!^{e:k?zLaQN, SQQf6[f3iTJ{U J nܴM֯S%={FDUFJH<ܯg'@*S:T‡u ?q6u;bZ0RgyALB T*sw4ݽ//g,("rtʹGjB|jM_e;wF_QβR;e"BAh`rVTX!HۖWU &\Lfc}*0pikMdRF.WZ7 um94>颱0M`Y"0ٺ2ϳ%%fFއǖ燵Y/HK; D 9:o%%:*-79/-t`h"G)&A`( , lۑo@B(L*Q* "WM~oq^k`KUDTKW(i\j93jgH8CƎpgkJ+->7Ç&1%3ҍ޼eP-~2qӶlg'rw;XbyIR!wԨmouw62)ud–]{8^pRzwiۚ.Me*,4j$Q{RaۺPo6T7;OXAº2WxXlJX_/n#yzv=ɤRf`",,bc٨QۯuIرgO&C@B[hN×2̠L1uņlApw˜2`NDaGFt-#eA(B@D"P2 D "eA(B@D"P2 D "eA(B@D"P2 D "eA(B@D"P2 D "e}([N=Khv e>sͫ >jgG53mByoPj𸥧-]'@st7#i=zǣ[ Cl9s7KӼ~~GBZ~%'׸x:mٚWgQBs˟`6YsTD5Xr_:tnT~\v ~5DΖBYqĔo#DȴnZ[ exh%YٙI۾vͿ6ܙa"-Y?͈. '9k5;YM~r~լ.xrqr*xb*C$wqqdQ8#oտ&-,|s^7YM :PaHD|D$0bҒvm9+ SCkuņlA5CI2 D "eA(B@D"P2 D "eA(XwKxZ,P&-]܏m% "P2 D "eA(B@D"P1,Xн{wNm۶._"mX,6m"1ctO:cbb>FF999>I._l][o Zk[&L&/v7t/20D}Y?GDD,\pŊ5W{gy>|۵kW%KݝB;wjWTTHѣG\*--[͛7V;/_~ח,Y%.pը[ib \VC~ :tĉƍ ]b?"zkne˿L:tFYFְA0 JԞ}bŝ픍?zQf|BS)h4[З~AsR$}>]t)///))qrr… >{,,ߤ]vmذRSS]EH yq+8.!S]Z [g#]_}k"[Ǐׯߜ9sn7jԨ êW^yoLv uT*w5v *Ȓ}0JcG<k9q"$I&q2GDs=Pǻ8i.fYT8Ӓ9xODvQ;C4?<]ݐKR8"JoZT[wJ2xƯɵ6fM3G|rR;[pPP+2,su IDAT۲ܿl5G_Cԩ}}Fj]ۼysHHȬYVX1hРgZz-[6ci@\=|Ғ?2,ש9FDwOs'>s"˱{Rc$K/T2~{zqqqnjWBLݎL5e۟6|=F"$^Cq:bYX1]3YN^uı/7laUg O}7}{G:͚zgHIMMi_1OQzp(5mN8Z2e֭[쨾w1EM2 Ge#}=<ܵ ~zN/U ]g|<8uևWn1ڟٽ/kO_;stC\.䝏;}"uھ]͟KO^)LcXߞ5)`@ۈCYLVɌ4ƛ%{Cg-~NF2fPцuq;;׸_6{ꩧ߯~UUU}'.\?2mڴZ~g]5n#{ֽYu@J@|<;?V׺]ww4,&:ȮywǵIB_]SzZ3giFTh )y[G?fm+c+jueNcYO RYZ+qh5w^#F(//'{FGGϝ;788811qΜ9ΝS(Ru{w7~jU0w/ל>N q<|Y"2.[wH1bʶGsweMX3՘d̏ᅑ^ <٭)ZKNڭgٳ|CV_8ci\¨DDoEm}ƽofg^kmS~f#e{{_~裏>ÇGGG >';5+4񻷿I1~tzDG]Vo+ii墍dDD\ʎ5 nc#!v㞋[8}@8ƝIFYꜙzF ?՘gD`~mٯ g͚~z륿KV>3gj! [n??pԩ;(GŹ/F5OV2mCaǾMQDHY>7Ȋ?ڨTNk h=2jzkTec}rBbkiJF6z4-y 2b֫kcܝ\~uoS~lݺ92r}aa-RG\᷽̙3/^ܶmӧOKRG 8O>Dt…:ԺF@@@|||ݏf۶--}Dwfr-]vz7rwީ~W^RtժUHd{$'L0a„k$ɭ\KlxNbhm'$VN̺wh)6ʱ*-h P޹so*((ٳgO71 .\`y^ ,X`ʕeܹN{оkk*|llޅWջ$GK[gs۶m{G䇻bذaGtttcַ{7(33~={D"!"GoDDD"H$<\. t! Rʛ7o~Ghzڸq!CAsԩ|K.Dm6IJСCCCCS\\QiiW_}5cƌy~ڵ999stt~h4]6//eYdz,cnjS3uk]vY?0IDg͚T*Eo\p5/'d2-\߶ֹcǎd0dȐ]n߾h4~2'fϞ{ ;:-˼y铗WRRҿ뉋믿 Ehhh(RS8֯P(mfX9rd```ݓxcu1cƘLW?e0L9DR1#Gnݺ5**k8NR͘1###cժU;vܼy)SjuqqeÿK^oooر:LdN1ޞCU?,̙31 ,SNuww?z//׼at,[].[,&&F&%&&iFT݋5߸zk1Tկj~~!C9rg/G]xu<ߪU|رciist{8h㛪w,_ZZ믿>S<ϛLzOb{M/X3,Y/^S2O?5k5֍Fkw۵IR@d2Y,/X-++swwoӦMBBB=N8aR-qqq FZ]-[6nBDqXX֭[+**jnmrn/:.999444..$zMY>"Hˆ'==)6vLDNNNփ(<<|ukdSfŋ_òB8w\ݓhoo`l,7L:CY3fdӧWgCA<==NZ3w֭wqqqssȥK=+ʤZt3gLMMMNN믿O~{ 0aaa'NjFߟڋF\o5_Լߝ:wZ;i5y Z6?~:u7K/-_=$$7XS@@@NNNQeddX<==J͛{U7A BԩS~ʈ}Ç/+++??Zm)z5K322bcc;wl [0婩SY_gff1 #j]ԭwYzHz=?E㛪W@@@vvvffun$=&GǏ?~|%w2l5yɓ'Y]h4Ǐ?  >>>O e4k֬D&/{{zxx4m%w KF.jomzn=2}1tN##e{B@D"P2 D "eA(B@DD1+Wt -LD%(!+"P2 D "eA(B@D""O++h4t-eggܖemrЉP))狊{*ʖEgΜJIIn6keٹsWJlZċe6mBNG(@2<҅ Bp%>J69pH%@H@D0R%)0Rl F<[aj-]ib7{4djlg*yC+T0 0N~Ћ'VtI.^~,f|B6GG *lǂ'==.5TYUz(6'ߞt8'BDw:=?N|zNޢ' xw?'#׌?xGqT;t䈉p'"y!_^}@DW& F:tΎz,߈ +שM>(M^ioUpD$ߴ=d_ uizvb u OK^qn֟2mqn?t7oq۾`YӧL`RƼԺPuLztf|!BdNo2{94%\0ƭi/g ZsOOztqEYKPxy >mK~Oߑ^ZS%:#Lj{.g[w9vO@\j잼}dIJoO/..Mj\֫h>c}m|^yiNّn, :٥eW~ϫcӸV?p\ՊiOj]6Y1]OUg{M(!ҊqYuԷqGwv_ YN)/>>Ӿ5@8ߛpg?ԖV ^zÊ?cl(>0n[~:j]^A7;˶6_ _#r w&pܯ~ZȂ?[/7l]aۙO:vƷ[nX:~⯷%.U'kv/p$+^ |ζ-3?lߴݜAdǭڶt>ߒ Ҟ//d·~]v~WE_ytI_hvCX6] pgYά]u۩XSi _h}x / ioe|޿;GG:12KӗK-RGnkuzف׾ȝ %R?HkǰA=kR? S^ta$cgսStr6q &6b_Vc$"ӱUk2&f;fY뮓o>Ta]\Em6S j6foz h=f.d.g'k]s}f$oagn}gM~f䗾NGwd RijNU.Cy' 4T/ȯDN}n߉BHqبBQaa! oS~y{>ΤV? UA,dUc1?FNz%0bdoOO < oNe3Y>!m:DMcO7oF&"xx{(zo3 ozwaYocHov H2OO$'"sy""CWq,T Wl/79.*x 5R,traϻKNmy9)o9_!߽Mj%Dd#:|~XIۨL+m4&#"RvٕT`NU,[,g">{C9F^}Y9CY-Yqtr6zOmQ?61[nsq /=c7ڸ3ب6)b:g&=%]1n| h!~_ID\_bXۃmI7W|˅d7<MϲM)e8rǓP!+bP`52x52CF @ܥvyސwkCZ^wmŶb[ʳ&qlb\?m>kNjk7&~:&YyYOݼt-_NѝBHŹ/F5OV2m712H)+x O<4N=|ϭIuc>ڛIB5o5pJ=w'7lSec}rBbkiJF6z4C#۬oq1U1NNCTsfX~fC1nx #0k7-oSw kkE}_Wbƅ$I־T,ߟxבsjssx] r˺?O?wf4?;ŬczE[yYΝCNg{rç2yuO5''=ſ&qӉbn9`߾ذ#.xLli!HqK[mҦͭ~k⩶_rsW\)_"Q4{; KXQZZn䮭f(*exSeiq9kh+H]*s &F㠐I- cj `Uy"N(cLey+wPr)Ko1+KK*ͷ8MN>HٶmKKE¶; !,~aQm5Bށ[m]3œ,ȼeY׵i*-*aj ,|ysy^ֿ Eyn#`(+0YPSXQ~F Ē㝗'-xw6,t?%ܐ>P8[txآ.%!ye[RV6W!l F<2-y= `K0R!l ye[= `3,D϶ߧ8Տ!lOjj}[g4ڶt! `3:uk߯CCt! `3$IDt!Ќ[w#"P2 D "eA(B@DD+Wt -LD%(!+"P2 D "eA(B@D"P2 D "eA(B@D"P})**<}TFƥ2"Rվ:ttvvin 2q{}BJǎ]ZR IDATjMᢢ‹6lXԿ4[,~}^8qJXXZ1L&IքOTeeM9kJ".pը[F c 99!.7i>mQ_U|3K.ͮ Xj(\n7t0f3<ϛfbbK{c 0 +S{S<i?Yl~xAI;>և:46F5&jKl2 R :x7 p!Q D{fSUṍ7=?xK3ʽbnqUmBDD{߉ӆH[FeԇBM]e|̩;VdwyGbf:۷t̩[hHRWOKM8UADGhU*gc?剨'];Ή%N2yϏ9"2艄>Iu5Ϣ:,SC<4jg11}Dd;o=d˫෾|]Cّ[͒9zzӿt .ު>IH(9iQm5*{&jw%}=)קSH-g>M*8󸟲rLjNl$>c>W^H<%mIz~|ik]4c3z>˔aftŻ߼i 69ⓓ݂B_ 匌Kx^?ϫsr~\?.y/=`e?%}c?ȧH/-I)ÒO~ʑcDt=y3-'O .5vO^>J$~G%㷧~5.TQ4>Ȋ6>4'qH7Hx Ҳ+?}~ձiWc+M8whjŴ3 k.um3߅N0Ug{M(!ҊqYuԷqGwoNZz/['L0fgRx$/;j?MMMMM=Y}G9?Nx)T~]7=.~-)NvQ|naݶtԺ}^||"%55ק=O. r;; U;wU?P(0lV8`V%Y*댏'S. 8Ƶ_T3E{c"xܽ'wtbeq/Z~[,i!}9O;;+@J 3֎a~{Zפ؃WGaS^ta$cguvAu)a:Tuoq1qH/+1ت5Qxĝ_touH7*ڰ.\/YS/GX"ƹW§.!j|S|j}DozkYt F(KYo?Z\= 6|Wid7=&Cfc 2\PSf-]gƭ8y\T9g}_TWDRQD 1.b޴4iuiѴ^,˴\%W4qEasAEvl3y|9syg9;jd*=kk?yOήnR߿w*vmFn 'u{fֺjC#êui13⇘p%b7疄2_\W_v6J[C7*/Ng1mlW'nmYՌÅ#"so+MsN.\3 n<5 eҤP;G!Bx   {PaaRi[9w1κ"+#jLgg-K$ȾU~w kd}dϚM*clbNQ7cߊ;Wzb+X;qn9{32n\}sr *xP<TDDay)\̽Z i7g66ϧaԦѤ{%dBh)uIc{1κ,?(-ܰ0m fDѱh<5 eGG7]w1.rNxm#}З]0ieg=5S^' ^?:ƒ#"H@eیsav}1Kw{tl5v@ S{sCeq#^H[ˏVXYt5 ^,+)%6ގ,EǾp\]WyPí}>inհ1\QM]u"B$S jl`A,M0ouzkOMngE7K9?j}؞N~^GW8{[?}m*OD$3+/:o3/̚[Y0׮kko<5 m22n(njy?o~\(R;̼ѶSu"RjV۶,4-|-}dOL;20GF$ jzI,Kivffv_6.1iwwh48GƘopT$ )2zǜGF>Z̺Oغw(_[So]FRD=<1hi؍U=MlJK,ufr1XE1oEI=;+ͷwPzi[q5bm0;'l f,0[33q 𴘽{wILIII5qeFot!D&"aSyỹdzŧ,|[;G5}vHԇ(Zy.n~&۷[WGDII^e2O"JN>Fzq]Fò,˲{ .bk*|6?V>BEzɤSή*\.'ʒ⬬ 77w_$Js)gaa$@_Ca++={{zHOOOwJǐ筭mT*l8O7Ճ_z<|I_CYKR[[TTTh8BallY Gz\2 #bh1> !e A(HB@BP2 $ !e A(HB@BP2 $DBy$''G%蘄B^%9]WP /$ !e A(HB@BP2 $ !e A(HB@BP2 $ !sPOŋnJrvvСKh6=e~KKֱc_Цpa۷o aY}O?m/++3&O2UjZ2w;y^ו~&/Tu4x'~VffV}ql]QMIUj?  (^x!JAA1,lL&OHoj  aS}B|oӷ>nG_8_ ɽL=""&/n.BM/CΝ;ׯ///AEOKZXx snSUܹ#xi*|v-2""ֶ[sQK Qz||:֕5ǧSrf4r2@ck.#"_>u271pI|@D?8'ICDss; [|'~2"O_bifnnicڞZ,Ċ!^vJSǰ%'D姖lhRlO\NDUSr.mܼ_s Q&k׬0/+c#6֩M!` /=~Ӥgx"^De5`wWkvç,S$j`:yu`py& kβ*1onm|̮ b+Ӧ=7矜sFhKS Gd?R_>=煙+gy8QW/,~f6ޯn?OG/C9+놳 (4OxgǧP{Nڴ">a6L=Y|ʿ"'e@HC ه \2ħ.i9哻,**Kza?l~iqfڰD"?_U\sc7~(6+sc!:&aտm[%~y|D:u:7;WIDD[.3܃#ƆQnݒ|΅IS#pcܷ>C;Nܓ64MՔ5lRjjjjX""yC.MŲvbӛOiوq]ν8W}햞ͻy#ߜ.{ ,k;OK/CT0F%MnZ3#h!$orcڛYEݏlO>qw„끳fwDKj@3&JŌbLݸFg)kxrz{X ZFd;tla[y<;kJU0QoZD.Ϟ)Y| d~~GNm26<_mD>ekvء,WJ~g٤VrދۿO՗wܔ`+;u|[~~QCm97c1Ә7,zWW wy6${ewfjj^bHib\LDL^(Duk" y9 7۪ŹR~N@[P㩸#Gv  :qIA,vKGtr:gGZR\cCP~9=SW223ծ 䤮P~ZZWchdXN=m2acWdV 3ܒ^Z]5Ά{|S_5XikhrSFE垿i:ꄼ֭-ypDDB~mirŁkF\)+&FYXXT4akeik si.3fuEBF@,YdoqCF[-xSY=k*7;KT^C?2(8ioyo8d8{'_n q7N˶ɡ.ƬpsE?;[@ U}صC|)fs399q '324d&#">zhdRvOVmcM?X֤{%dBمS6^?Yc{1κ2""" @sީb"of,gDѱh<5);::ݼ]u5>rrr~.a޴y)[fL?$t۲mF}\>꘥;ԽCɉkr\ !˲Of9r+H,x/UoGcĢc_m8fiME>tjؘ@{pII:[!PQt؎)u 5W6~| &o:5Χ&7ȃ3"ώ>cllW'k?UVOD6'"D kwtݙrѷڙz fͭ,k׵7zp^r۶^7rssD&ܜmz=U'+EoUmOLLrNȔC)}dDrߐ@ygIJԟ`gfjf?;{e#|zz؎VJSC?I9f=0dyZ5Wmʻyj1瑑e3Te1:!Q'Osp ZZ>6vcէuOlc~\ṅ*,xQgsjOw4 NEv^~uuM𥷺X[ '8m-ok<-f]w}}tRGR鰰AM\Y>>llla40 Q^^nrE332^H9n!R~+hyAon]E%%z9Rd=(9w5 ˲,j47n''_dD%gR!?Xu ]i~gΜLJ:Rr",))pssnjjJA4̝9./pݾlf @5ٳgی{JH4qpp yFR̖~~|Q=× 5T*MEE 8S(Ƙ}%0"C/?hP2 $ !e A(HB@BP2 $ !e A(HB@B$Grrrt]I(u]<Νu`@BP2 $ !e A(HB@BP2 $ !e A(HB@BP2 ħ,3yQ릿R0rʣyx7 4d1ᶨX>!/{2nŽ_R쏍/̙f\Ƀ*?bt]PVt&e+#;~ի /D:X$>O hӴ`O c]ȵ\>͌[wZYk[ jkgnj3x\k:>e2e5agYO)qu9I"ҜIn<Uvw/ RY%K٦_9p0㕑ϵzr9)`WUU%tY~:jӆq*dw/v?EGqtCF_!4lu=iCcINrFOQtό?!}(%#PH|jBz{RK/,^ǂ e( woukm9u1MXάKciwf2S؈P|WG5y W<C>jބ*1i?7o)[Pr TX IK#&_5煸%B^oW;M%>k {++++++kI Bj̙|Iϟ7o_E:#uOk沐pqZιR~N@DZXi` ZY[h#542T9'7gMW'l[FiֶU][I[ %qG ,v]v?_qIK$6$[>&1sL"5k֣%-#IOG\\v|{(kkg]_5Kggm6${Y3_cH_#V-SP=knַWK`8a_ R}b4[:^X+s?Q^Cf y"FvJb+D+SGGx4+4!T_*5``g7+) m˶rcP&s@2;x]<3g{语_3sX2Te>lq!g#K? v21u _E;ٳyG\2.n}ߋ8h({* 4 IDATtWSȈ!RMZϟ?/+[{wILIIIkx۷[WGDIIZ&2 $ !e A(HB@BP2 $ !e A(HB@BP2 $ !e A(HL<tLBlooYt+P2 $ !e A(HB@BP2 $ !e A(K{/u]H~&/Tu<32qպ.%=L^\TŒUNe*$ }}3qo]\{adr֣MٝK.{=Oϑ212G [{_Nߥ sDTyt㣡y?4-Xi5r;yOk3V'ĦVV%=::kﮖiSl€|&,<|D`{GKReJr"R>xe3~uC _>u271pI|6IbщCL* aKNn)[JDk[ jkgnj3x\:7|J#;INr_剨ϟMCg9|c1QBEe˫NLoƩo9}wD$0*|[o޹6 ijĂUͪ 9z&9uP#~x$ǞoOng~\KMM5껜O|ذMSd߽"FH囶!?l~iqfڰ 6؄-%"x×of0b fؙu>E=3P@BB7C" ẹ o(Y[=ʦAϨ-Y"~@Xgެ5{wf2S؈P|WG5y W<O_ٺ*e[Ing{/n>I;EM刈w Osö|yvtה8Om˹iorf]Mv?=oʆ)kxrz{X Z`ovQ&tTIq~xOꆚިƺoOr\0!z=/.~9$ i?aԆyU[3$23arRẂZШu'pAZ- 73n- muUde<^mzڏYbvC7عpqjsqw<<>';W6ƷT$'7gMW'l[|7ꭤ[P㒸#qg;ɯŸ$ॎ;F{|ONsKyo(3I^}Yo( 4wN %wKs=ghgsrmUuWPv]Mu1f+{jaD.Y[;늄|Y"3߶I;WvK֏3 >ڪl̸dd#kFLcpCF[-xSY=k*7;ՋAgMDf)8ϮcD}_^4#II:[!PQt؎)ŲRjh̒Xt ǫ>Xdͭ,kkOnkd/forOϯ2tGz@Z~,ʢ'o4!T_*5``g7@% m˶rcP&o=g2UWb2,*̄Lk3Y}ֽwFښ,]|#^^k-;+t cǿ=r;on 53~x|o:`'S\=Ǥ޵1ԮO?`Eȸs[Y|/2v7im)!>2"oHTӳ?>K5xQXJ_)ihiW=˘{wILIIIktaW5 "!MuPxkA~b/$)!{L;xUk{{pb1 S:7=no3?whsyQ^'J1BAE36nųfͪϛ7{k߾ݺ>"JJ:]=w O {׎;K~WyCk{TM3gOj/D2 {46-c3gi45 q4͇~n>> )\'QO>=l077."xuW?}٦*]s9k;S5>ɝ.1pAQ|~Gz- xС .޽{˗/ jEfϞSPuZ+''Ѷ4q)oxnܸQ_b֞T!g?Ue{V>!)))!"AXZmݺ[ܼy͕JeS:y>)) ]\\T}{xx$$$899r''ЯV+//W*(^pA-5jݺunnnZZnVV(uv0\.///Yڭ.++#+WԘhSGꬹQunHk* a{;559==4Mrrvyaaazzzꇖ듛{nAAڵuGMMM?h4e_z%!Cl޼Y(Ç,۔1q%%%K.733#M5ЂE뻻zxxɓ'[n]֦M3gάYUVFFFA\swkFgϞrQFQgG,v}ʕѵeaan:333SSf]PQkրΚU׹L&{7nܨ a(رcϞ=ccc{=Ryʕ .L0e@ /$gP2 $ !e A(HB@BP2 $ !e A(H>''G%蘄B_}g;  $ !e A(HB@BP2 $ !e A(HB@BP2 $ !e A(?=!~&Wu]  8j]Uk麄L<5͸ k1Lnۂux֭\&Mx]ׅCsLDDBƒ/g ]Q6~ʐwD ׷W'.CO-RTz{ xq~a΁<$E卝3BXZ8'Xxv&*[ӾZe& kβzHs"|ML,\'Q#;INr_剨Z{-5krm\>e'FŦ7pcܷ>C 9>⒜{ƿ1Ksa1O|?&?J /-1nӹw2窶o8^IDD %v kF,8QQo➴1G$njdz9a6L=Y|ʿ"'e@HC ه \2ħ.?_te e>k {^ٔ'1>y{ϸ~pG(7%؊%b,N𖟲2:6\<_5% zFhqº>{I>#Z12˞~՞%"l]βIݭ$3  l_P[Mx&yۖs]-ބ̺D/~}jZpoR~N@6i?~4gΓ7 _ʌT@N (nmYժÅK!"fM%z/b3[z~=~HWVȹj l$HEΡ~|1.=xDo(3I^c&u]+!B.q(cT>#-CCf2"3g6A6tclۛb 7Wm6κ2""" @ " {NNcת+2rfJ$[0j*nmʂWYSYߩ^uZkc躊DJY~voVn8,mO(֜/ V1ؙ)+)%6ގ,EǾpd퇾utՊӗߦDD\QM]u"B$S*-)[fL?$t۲mF}\>꘥;ԽCqВ=#L|W!*GL_),ufr1XE1oEIFPD=<1hi؍X;+t cǿdމ]nO+O{gY5UYFuj3}֑;墿 +{tWSȈ!RM!N"w~;~:#)tXؠu>es'96w'}u}DtzH Y !{Esp.uH@BP2 $ !e A(HB@BP2 $ !e A(HB@BP ]c e{{{]Ϣst]A52 $ !e A(HB@BP2<;P }!I*4cxŊu]0 pZ)t]@(}|jNۀ冭tUí'w0{k3Spw }뤀? r~揶0N6mepnF릭 c':Ü2@QLLDe*#QuU(V:,OB(_L灨 2`N01 +>^2U2%<eg69XcaEAEXaEFEe"Ih܀^/$\_$l}ӜS|B?WW;BWL_UC c c X!gf$37̌9S#Nl+2̌9J2Z)V Ѐ 0/J?&z8R.2.c_˫š%Xvݤ 뺊Ʊ X23JcC.] o?xL zmuWݹԃrQ/afn|^P6ݡHDG9L3.uEpr$h*52ygS(+׬%"20rkP ȩ o'&6QfwΩ՜HDdQyT"˰D ̲^TuN/XP!g wۿ}dnbb?/\9÷-N{zutp;[S׀i]ՎO-RTz{ڰ]}mAmMm|/8+Ԫ6_GȊSDLODb(OSie;K۫W!N>eɀ<0K7ny卝3BXZ8'of>e2e5ag /::?8'ICDss; [|'~j鋕k֮ZNU4acH1J{e)/ݕ]01D_!*xY+8Vԯ~E}\>a6L=Y|ʿ"'\lxvK⒜{ƿ1K"n_U}hbÈ3:\>e'FŦ7 Qޗ5c[}߆ IDAT yWwcSa4L&"Q?B|ݕs<ٴT\D$׽:fM%Z/bڏYbvC[rstu<,fS%"cߨ(&%v_>#ļ kVoTjQ)0iڲU X=U=??TѡnAAKXĝ:~'܃:"i*ʿW$YY2.>#y8H,R$b(_,ma5PԵʬuEBF@,Ydo˲*ɚ!"f-{yuaɉ{_}bx r]Mu1f+{>:l̸ddhM闈H.z gN8pG1*!33D :هe BTmVc'qu"cH_è}Ϸq* ^gMf}z9oƎuuEEŲl^4[|R41uS=er @K:Ѯ}o@( Meչ2NI_z(_7{eN1ewgV_tEjg[^~ԤESWȭI(~l JJɪ1Kbѱ6W?~7$1W6ͦK̢#7rai3|ǣ 5W6~| &o:LCuֹutՊӗߦM)CJ-fԷ ˵ YC;KYKJPFF/?-ښ[^qL$"Q+yQh/|B(2|oZ6`Ƽ]c'{rDq5b=G%8"Y=˫c'ly?{[~$;'RyGQ 97\.pR n, ل~Ţ_gF.aE1o}Dz>yC~9:܇މ]Y01fvfޡ~+M)wHw}dD(b[z!$2 "i*Y8~hYADV.T*W+är2W8+ÒKG k9ļP5f]w}}tRGR鰰Aw/o{E-<ȰI9neRi6%rNN}ه;(eZfMLUk,ceH$Hhzݫ/~^amUQR!RFKrܸgp;S1gc{+X?F~DI$Daa<nSsFe:0wJw4wAk[Fs)+s->×6y?][t#?Ň2F $eHb(>%<v@( #rvrn~H\݌oO2@ѳ[?\:)uU1@'S)N'yecìofdȺb=oY^z̆pۢ>x^q \)x]OnM:i+NbnpU߼DqC~fC\;mz?nVt9x1i̝`J>b^^Kih7go1M'\?Ml 鄹n5H^[9d85ګ_\NL2` eg9XOg>D?ȀK&,} !\S9'^HDE HV)fRBݤbJ)jjcN9z %Tr)Vz 5Ts-V{-cɴJ޹i:w ?ˆ#<ʨ.DIH&}&`fef_nQJ+*vqwu?vs^3[IWYp)C89#c>:2^4לb9͙m>gI3f |3w2o4o]挦Ȝ=dkdm$HۥKMJbZK ْ{9GiS8i؆k%s6aڲS TZfB\m{kV%.~.nHvEeD XCQ+4<:fg^^!`>$!2([^܄wm&DlQ0KF7 rEB#;~ܪwEb;&gUS=k-9}&R-Q\T&{BNkF{5uE Xd# RX6S]m0^d9/ ŧANˡ͛2zkB4/Lˉ ҩfn+{y',S#kɲS<;J"M #+AN;qQךE6I(n_O.6ܰȃS<~[%^C.="TlTKE?6WEUzʝmm5Csg:` uՂ(-10Gи2()~UwB48Im?#&hRN+Pnf&֌3莲ZugSA*slŗg+A[)@)y =ԑzQdPv1 B,(۱vF gC/ ǨS=( dX RI\p%ۑߎz) .u2kKnbI^)o1@ZS, ;Cuvh&&wM vG[K{bi欰?FBr.(z"uj"]g4` #IH-\HuZť6l<+]Ւ5ry%݄ YsAְ^)֡R'ٛdWLJQPR6П$E]B9B}&t_Cc욣BJsZX+rYMGJ5 [)9EE+ %aut6O9 uu|֫`I8gѓZUWP9Hr[:]@)f 7P!iĪ%-4ju(PR;Y4O]: QӚB>Qa-U+Z&^ul9A jx[tꖾڀY{l3Q貿t/Y Qs]`+h\4TSȋ+ao\T2"yjxP×9vRgL&)D(ffSd$k0Œ&K9T6/i e?F">>O(X4D@,0s^.LnڐZhBqe7FaQ~7+ |>ym2R_KԂ#-T@ɷ~ByU#u}r" 2-TV䌶+QT;Š _hفC6##Єs քvOT֋h~@u8u9>&!#-]-xՔ{dž>npx/=RC5BƁjFr+TYAuu?>v&[r /ft+'A Q }x7 0$>Zp\kJq3?MVFнSU08ЉEeCǪ-q1|B .#CW75V;sA3IRa+BX[@Υv 9^<ybҎ*= ̺7=F-4 3p]+fxꇋ8Ivh|)0D66/X kΔ6J61S+tSGAiH]b)#ZƈT-HUK%Ou5͹y"R+WaپQVSYy (ࡎV_Tߑ(J0 TՅ:5$V$ \h,2nܔP0m9n$A} QtMYY3ƁptYaQ% \ۢ*;ԫ&S P@rT*7[joY5͌@*CqW<\r5+'4`!)5 EA Z)?Hƿ夛I^X0'XV3|rh?onJL+̼0zعq37 Q$mDBӭZ>)b B/nl&$ۯQ6K*\;DUzLYwxlA[O x7dրS ݶe|3#nz Hl oЌ_yw:Z|,(9+\03Eh2&7[} SN0aNÜc8U bFU/̫Bh%|ҚPq*oH,6jOpWS"V$Fm֬RSE/2!nStgYff`Qޗ(gO߀Cv,Up:v>3FzCSA08U]8i/#ˑ<` ->3PB?J<%"OlU *j@ #x~%U@(L 5؀ w-=[r9۩ VR/ "Psm_-arȮ&t:C+mD0k/CMWZ7W2jI ,,<n,}z%`Ga,G=ݘZeLNlGЕQqɝkw˅'wMi\{jlw4)"2z wKdc n6RG6a.'. ЊM`UF+m?10dw)k.Ĝf(aqTyg.95aCs{EgUVc')CcXC$ȴK򈨙y1,I\RQPJ`F#@4tjtKf~p?zeoBF}iǛ/_ ANΥe hBBPp7B6{Bpޚ+ Rҵۿϝ>ʰ3u6ש'lHr柆LU7[![BvLCC0㱊dUۢIx<30ϓ>'58M^K~B6QzAa.7)aB2#`kNGHXACtkԭs}Bx)" "܄ܖJ.7,4>{:Xf,mHΗ`{yf-]Qp`x>3Z4[ßͪbs\š{wAY'#n>H0KrMȲ4^۳ }.mxXxdOyߺJ>_i+_|WV`C/Laz c7j$iCCPICC profilex}=H@_[E*vkP,q*BZu0 4$).kŪ "%/)=FfjN&lnU~Eaq(9]gys*y>xEA xf>Iз \\5ytɐ)@_(gM9Ys{kPW+RǻC{r'bKGD07 pHYs.#.#x?vtIME%F IDATxw|lI! J tP:JUVvv*EDņ k4B/IHo;lHO3g6Ϟ93cXeqNm""""rqk''PuZTF<5mYiY&+nޫp*"""RCiq%x9L*}M_q""""R9CjYƝl-S0-)<HK 噯ZЙ_+.@Z\H-)*TPZO&;W0-K(-)TTDDDӲђiIaEiia +7*TPgYTKZɄBhi'*`ZZ(-0,Йzq(k8,.d{dZ@0}1N}Q.-L,1*TPZ\0lE)H 8 e9]+k_Az(0+B?ZU$Mk%W@?YZQ /STDDDԒZKR4li^ 5MNׂ(~ifRPREDDDNM-K -Lm67ҼyPW\@-F#F} TSLҼya4o>o>[KV)/~*hiA`&III$&&Bvv6G-""""U$nߟP faۋd!5jIPl0-颤ZMQCff&vVxMڷkC@@ vðFb׭$Z HM.*9gXVn?wA W㣯SFҼ`̞={ճ;dfV맜ܧBsFnZnMvv/%::04Oi_՗"7RZ8  vMEJ!M99/X""rݳ,LwRBE4k֌jժ [N}`W_boU܍gxصkzt'=-,I9[9+#-EXr-[75QseIO*6éPZDڵx """RNSBpTeggaw8NRR5jiYe+DKzhI<Өa2TEDD۸vz41 婳QFz5$>>@&,|Ҳe~>K @DDD*M(ud:m!0 '8FӓOdwU#I z<\w*s?d[RAӼi Wv6GV&v5D˽ߧge8]{Ҽ{BT\Y\Y8J -eW1_4ƍtӔw2Qܻ?=)/Sàk9U=v]QXf ^d]_DXXh99ٸsri4Ɇu aJ6ؾy#N??8ZNMω # 5uޑŅҒN{T`㖭쿌vjլɞu->1IoÈ+Үur)0ϙUeL";+p\ev1 q`N?lgai^Z eɐ%.7AGVydz/N"24GxK_nN~`팝 O?2???N9Za= E|Bg}ĺ h߶57 z/CE*e[zmË=9P:1Mݻ2⚫iۙ3S._WXqsjN{jAg8,jAAxNoiI Kb+ Kk=-|Z_Rv[Q6 ٲuwIH!ݓu)^=L[o矣IFdBL4y1Z ǜ!_:[? ZM}ee9W> ~[̖۩p8\=d t ¥t9zf!#3ֱ-лW%>^5|43_}yx'زu+Ccv]9'gԴ4z=eYe._~C]%KURv3iitڅxgggÆe6/YV:7Xu _\Z=}vիyXZY9/@K ">rxU/g?/>yb fu\R^/?Ȱ|\tm ލ`Y|kOFt#5, <ό7\ ZR,MG(o(Wẫ 1M {t+2ͅ=lZUNx)ʷ^YoNr?SG YRe5 DϋRo.#=5ETAxg:wze{38cGTJCF9xa\ )1cq2@-ӝf~AF|>~֣K{OYtr?5%.?]}>iΧ_/JGUC Yrqu#`Z/\a|<;v)ҩC0xp*}|ZEjȡ#G=-_A$j֨N]_éQ!vV~.OiY鑤%ޢ}!EEFD .~a/22b4]lPzțj`RTO7 11<0n"kU2}.C0 uCv߰4۵=qʸu_"';cG|N3v⫬^Wꡃf4-GXf-O<󜷫c=4]?liTqIzZ䑤eM'{ŀ޽,۶s1>|f:gO[w7r%)[QT.W6)Is.#&;+'@J UR`X)SzgepС'5ҷG䳹^֛nܘxh1O><3CHU ΆOʻҒHw=Fx랚c[^GcUP'i#3ʡ qq=Pz$;R)'~ƜO'~RHIKc<|rbԧ{G~ UaǟJ'8&c&Q sgd2{2WǂeyquMAJZOy!o<XVN8V0lD5j}$Q$5* X yBb"&<<.=z+ť"qi2v{z<{xggN;оu+^,c_|6G7АzƐvʢGW`M7lO&t#.\IJ+NnƜ$:v <#3';.ƍ+|J̆>^/)>WҴkKֱߣ50 j3NY3jԐL23J+XtTvo){FGXovb;kΟKػ?aа~}.Փ+ SBI:yYn];q>xոޤ1jCٻO`O>FF̑eɮ˗xzWz^FTN'~_a=~A(}h>i܇A`?33Έ+r#p8.23IOK4=Un|y^&f=h޼~2zjvb˜g>Ïս[fxHKK;t#4ғɎ`T$_+'G DB7 w?yɧj܈=q{,_fgU93c_b߁D5n<9f ,>zٷᅲА u6?]z ]Tlv;N_,ݦ???C6\}`*l?Wz ﯃ Niw`dU`3Y|O. `epXW`Y /6Z0'x(~~b.ϲnSvm-Sr'%0q`mmRG,,L}n2apoѣxLSzl,GҬI4s_`=ԩs >sS|s-W .s!ey{8M b|zvRi<ƿ<13q[>5aY&ݻϐY_3ϡĬ`*²,褐}&t0x8SߛIǶmr?$SR#aO j԰3239oÉSZ5x{l:wPeruօwo`kG`մi1}> Xh)oN~Ԕ$~eu#2q8qzPfM?59=wFZ5,U5S/2˲w?-cddfQe){*?3#o z -9˲<2V F$|NړNjqeL _lC޽Q&2+f;rrE}w'n~?{믽 {t+N' ^g7n2y1xLǓnw[uFgn۾}A/RI,Ս,Y&AlMYI$Cf(n;s[1*P;7TWгgcڲ|߷G&r\H ^rnhԠ>ea߱=&OvscngOcm*aO<_}#_}#{' L~gzpVF ,aquшey[f.7g|0]^|e WLsǴqĶᩱ/үw/K(ea.Rƿ2qy !6kʱcǼs^{'? Gs˺MmCobOY?w3lںy_}7\GXX_Tp;nJQ>a[iʱTz&޽z9O\'enZw|e`{}90M kJyJj~Q۵!iޚ_}#6}CdggW#>1Ukח8Nizp:p%Б#Sи-[ۢdfd ~[L}Loyv RSRIMJ`pFǮ8tؽ'CкE a9\<_uHKKgCdg;wP72ȲLI=_tvr\.mX-]Ʈ?vx(u⽧{(6۶g?>>nzLfI_EL Q sȪVH\.s4LrNlSY(vۂCB `dkܖ̌4RSd~ IDATfv;rȻ3;+*z sÛBVf//gƌ iㆄVAܮ F<OS|*K]=CcϞV vEZjr,xH>@dFaO:x<R9/a50_P>#IEz$O"|OKMhCQjJRXVfHK%#-`,x+'bBNemQoB{f%8mVm*Km_ֳJ=%CBvv6{ҤQCeI,uL?{HMN:nfcsh^\,<=˷p:e@z*(H,[^ݺV W9;r/36UK^;}lygO: [ETD*ïV!iq ~acjS0Nk(NJP0UK TEDVH g"o """") jU0Q`T0rYJ\DD ӈ TgTU6((((((((((>|ǎ DDDPQlظ97lԨ^֭b-7ѼiJ\sv9y"""`z66d0vUװp6m??J~DGGp8*:(7 L_7M+l6fv(+l6p8ԯW;[~\}0jW_!;;wfϢKH<~f͸i*n;w2nKVV_q5gNZZB+|Ï !DG5f7 4wW BNK`۹+kHNIa)X]v)?ʡEDDD2bsanmTg_ϏMiےSϲl ڵiCXlww?sfΠ_ ٱs'k֯uX64֮Ef:LӤm"uj32._̣QiՊ+W[жUkڷkC?M[h*5j0Y-""" ٬92&ݻy3s46͛Yb];ɓ^0 ޝ>3ffsС=m:mYa#k">!5sQ\d݆XEM6dwZ|2k&6o?au7mC55 `ڌL`vT*]T!~~aw07&44Jv'ݻu0 q4j؀Mٰy7ЧiuXf bcS6Ddd,eùYdit8rwgXRRRؽ'.wݺn5jЎӪ"007^?,wfV4Ngin&## 6yV:wDnػo?}sAOj֨gfܳOsE'bmeZwvx<ک"""`ZUeffvm%Q#~[`-_@fͼOr8czt\fѿo}qJ,b矻ʽ `ђeuJG}L h\BCHNIax<ӽ[W6fz׽ԫɂ5W ݸK-uXhےPVYCt4n70vK\У;bوm٢s~ǎD5nmy=4_-юJG-|'ZKv;""3BCxGN T6W&@c8vC]8TDDDR0<eYyixۍv*sݺu֑**`0}4n Mcǎt: t88Nv;l6`wP9 Oid2 ރUDDD2Ps?aiƖٶ} @T8"""`*Oxx8)|S67k#oA#""" rz % %*TDDDDLEDDDDLEDDDDTDDDDDTDDDDLEDDDDLEDDDDTDDDDDTDDDDLEDDDDLEDDDJq`Ƶ*9cbuR!(S T"""rڭ]HpN勈((((T)H.˲ n ETDDD¡4o\p`*"""ۓL -3LӬX 0j@i3 ;ё!yIWuT@U0sїxr$dF`P-BCðd, "3ǒ3gHCZԫL GVi7/l)*ȹ m1d:6'f$fPj3<4 HdD54Lv35,üsg{ LW勈Hq()/f "$(,BCiC v^Ϳz7J s ӈE/nX\JvJDz,}SETDDD,?_AOzlea$ }X!fw>TwדюS0Ͱ7mPveaY&%RF7Za61jEDun{siTTDDD_N`Ǩ>:qc_Snoq{GMfouJ_7naj׮]aܹ3ӧO/m2wܳ<333*I˾=z'ԁ""RNXȊ=}"ZEVCH"`g|hoݾJL+H޽IJJ*0*0j({챳f{.rt+"R[IH$(f9XrW{ GbQV.8NTR?B) 08⮾ϗK-"3=a#Q" GaWtЭ&&;w^k׮ӱcG/^ @NN<͛7Zjhтٳg7|CV eرegg3zh5jDDDwy'>ܹ3AAA4k֬@keΝ;v,;w&$$nݺvZ^{5ZnMXXC !>>4;v,111ԨQAWGә[:t(r g޼y 64?3ӦM?e˖ݻUVw'd|^ƀpݥ>i$F}݇⩧w׮]L:Hg{ƏϜ9s裏X~=Ç/cٳLs2 >(=.oΝ:D䬕JB)]. $~Ekr{t*__ .0oԩu^ȑ#ҿի7L֭YbEvmmۖhf̘a|>ϔ)S]6 6geE=x 999\veԨQ6mйs\wutЁƌeY8mi޼9id8ڵki۶mqiҤ n5kTTMb-ҁ!"猐 ܮSLN8U]Ls$ g u뒜[oűcǘ3g6l(2kƚ5k8z(=XV-vڅeY裏?rXjU>W… "<<ʡzx~Y#G0j(4h@K}aÆ>|eX퉈`u]ܹQ_~/ׇsH7x#wq+Wѣ%v 9tnNfF)A@@ l?e-lIDvi⧨2EL0gy-Z0o<."ۗCҢE v p:s;{L0뮻~ѣGs=PzuΦM3))?N:δiiS2` DvHKKo-w}7 4y5\.m{fΜI˖-ٳ'7W_%!!omO/{yۗ/:x]DlpCzd%{jVd(@~\Ǭn2_}o1c`4Mvq\nrrrp\~[:J֍k:DȨ~""IыqC Zebyr{E2W ]`҃|P'lk/"]*尿7;,pt:q8v`ټCǹ F[nM6U($'4+O#as 嵯v颢:f.vZK+H%m6ϟOrr= FDj }$jH6м~u3c nV w-`*rHJJbܸqԫWAqM7qm`D7c*.浚:ؕ7c>yI|j/_8T"KnXr BD9NOw1+??6P\Iomb-/ f$U]SH-"""Riԭ =mm%' ðaNBN9_ŜEqmZ|Yys:6뽐GbTDDD*7u뵉<s0Bq8p80{ =e[@ LJ^߲#mۅӯM8 kRzS%e/>_6'qkAձ """r.SM.ԋSw&r<".3U!Ӆ|~0N\0EnT vw N {DTDDD=ZңEhͯsrr";;ۍǓ{*ݎߟ-ԧTTDDDBa M?**Th8|M+ """"4 """"g<((((H'?v"ۮ ADDD ө|Q0Q0SSQ0Q0SSQ0Q0SSQ0Q0SSQ0Q0SSQ0=~(Gܾ}޳nqW!(iP_vv6xEx81͚qӺU,iң +'%WP-0Fvmxپc'u##}(:o@rr2+V&{޻^w#o}Xj5kr qɀ9z5֓@Zs a~HNIa)X]v)?ʡK\+T{zzzi}YXA6mh԰K-gԫ /"q#ZƲb*^{c*6-32o8N0{O?+/.whެKyvxԪM X|-ZЦu,׬c3 ʡx'ظi [RF 0 40MLQ0=V][7ofيt9<&Oz 0xw̘5 @Z3s6Mg1l@}i$vfO^cΝ gԩ]4%˼뜑Q勈Jj1-ݺҴI47oֻ^HP)XVmZ{׮,[Ew>BBω>GN˘~[kb.|ii^ؑƍغm#'ۢ%^ȩƔIx@?>ٽ3yГ ?ÿ4f]_`ڵj^Kո_~ƍ:ֱ-ܩSu~e t=e+hӌ-[m.0pDDDDTNpSQNmnFt FDDDLl6oc?/a?/QaH<*Q0Q0SSQ0Q0SSQ0Q0ġ"תDDD䌉mI`N.P!iv" :/"""" """"" """"`*""""`*"""" """"" """"`*""""`*"""" """"" """"`*""""`*"""" """"" """"k IDAT`*""""`ZYeY*2p*6Yxa~) e{IJ'+3jTY Q8Q0=5vm;|mۉ`2H?¦˩QvT|ǴxnlXMNv6]/O&1a۱ѸI ]z#';-WqOzyƍz$''|㄄0u پLN=z'}дiMdرPF  D\\Ν;Dfͼ-GfL<ɘ1c k[n)0_dذa\tEL<fgg3zh5jDDDwy'ynq[o- ;w̔)Sٳ'tڕUV<@ͩV-Z`vyLjVZvmx %+ZERb"[Z6N78pl6Wh"%&&[niԩzwԱcGnZ .TvvVXCT7t"##ձcG%%%ԆnAv]6l$eeeiǎ0`e9ean޼yz7MgUrr{=OwyG]tQ-4u*9vXKM4o}ᇒG}T}U\\F:hƍe͘1벪kk]k@z" {ݔ&I&M?O9NM2r\ڵ3ͦ+B;wTbb:hСZjmא!Cl2I5`1n]#IwaٳZl-[jذaݻWNSݻw?Yzr;vLoW*,,iYյ`3@Au^wܡ9RaaakXNp{Y b_˗+ @C ѠA|nСCtҥƗT儇0 (##C:p?.yNSNX, V~~zkzԹsgxveUֺ)$y>ZcGd G+,"Ώ)X,z'`ϴmV.K֭sp8euY޽5|]V:xGO>:~nݪ͛7믯U-mժBBB|JmݺL&֬Ys* 6%%E:tPjjꫯO>jҤf˪STP#.Q}r8>:veگF1ÇkŊ^oÇ͛ua7N۷|ȃPWVHH$IM6W_},ˇnMcǎՍ7ިJQr*܀=裚8qVZ%ݮyaaa{5zhmڴIG9.=sZl={N'O-ܢƍ+//O3gѣGh"m۶=fý.ִLQe(5L TKsKFѱ ?cZpo̘1Cչsg>$jɒ%޽4{l-^XN]]mjܸqذatwxmCu˩ׄ 4vXEDDhСھ}{ӧO>0`6mxmS㏫M6Z~+!!ASL3<-]T]wǴo{Y]vu?gִ\t僓re9N9v9n{nݺUed,{ϫk4MYNrrrt=2[,jݮק?9'NŵhFbK8E%%%i9~L5iDL϶lXlgϙnݺ_~~~VU~~~ZG[,fPL&LlVdd>m+q"E+0ԕE9Ù(E*22ccۺuZnM(u emcbb0?ԡ݀@"ԸYܡ,aJKKӮ]Իwoeeeiz( s,FFF*44TaEFo%dXPC)\6l5f5@0=7/Axhs'8RRR4}Ϟ=i& &SzR)jN%y`z΃*8 SL)S` )@0 ` ԖI^ƍ)82Mq(S` )@0 ` L@0 SL)R _+~n2<^@0MBa*?sLE`E4֥M)$F8 6T2t4됚4oN*I**WھebbԲm, 1NC;D=)4,\EEajު*-)֎St8j_|Q&I&IEt:uꤏ>3))Ise0 \.ݳSV:t\NS$b*#=M.G[G}7jĈl@0Ci9ǎd鲎]ݟU7\ֱfRAZ_oZ]uU?wӋ5>_qM[d6Cfiiz{xǛ3gn7͊kB i6R=#j۶ z_~Y=zPhhu릵kV:8͛7+55Uvɓ) ^ᴸHAA?]~3qT\\TP_/ 뫝ף>}*..N#FPqFqu]\r^~e>|#qĉzw.kرի4i7xC111mGQQf̘wyG]tQ-4uT|srr4o<曊QӦM*99E2z\2LN7yxpΘWMnկ_?}Wb}g뮻d4f̘J9v-ZuرcJOOWaae(88X͛Tedd(00з~f+ZU;+өݻWZݻw0 nP>zL( PEnx8E 6j>|>JW=~=Z:w\+kV6MofhX!vv9N9J.0 edd:~8;# =`2=H;7ӗ&>?uߟad=z}Litt*.55UzWWhB|n&S t<)))\G֦Mt=裏jĉZjvRRR8WL/Pj2Fї(=mǡzîp;K{~TX1M8@iӦ0aLo=7M2Ew}4e=3JHHҥKuuz|IА!C*=OzզM_^|}j[o>}hڵO{Vk„ ;v"""4tPm߾NQ>P\.PvnPiiv֭[u_F6"aFӔt*''G~#Ţ{}ϻT\jԨ,K*))IGȑ?dffI&*,,+-(ss[nǫjVQEf= 9NlVdd>m+q"E+0(XT\Tܜc:y@.QXT"##=6ۺuZnM(G0c8-;#???(I%%A*(eҴk.[YYY];-[jС\<}S||h0Ocge8O=<:G1C(5L TKsKFѱ ?cZpj̙/ԿuMǭ^{5EDD}߿ڴi>&77WK,Qٳgk>նm[wpƌ3ԯ_? 8P;wV~~>3v@2?[P1_9N|pr\t!.áRv׭[lEuyu)TNNGfE۵'áw$өmըQ#Y,ZSbeu~P/|~ΝU}˦g%v`8{\uMxZjbqf= ġZ'Al6+22RGKeֶ8^,I*.*Tn1|o~?qÏ~|)&:N)(, bgK(00L VG6[:ocat n] {ƀ:7(0@-[j?; ַGUQV-`d*KIi,wÄ)I\l6k;aRvdiiJJ`PW0//Oo/mLMUݮ+>rDs-+;;[Ѻ4޻e>n{^snu/ԢE37TRRwutxj_ǨM֒"+Zn5[5je6dJKZn,V} >MmL٬ F]7:O,m~UL>+-t!BCղEs @08jJa68GFV:tu~W??ϿZ' ?_bw6*!:vHT歚=ou?WժeKIҤgw7mueڽG?Sǎڰi=S:vP+]7ޚ vmꞡwj4^۷׆M)>_Ծt&?Pj ޓ媲~שc*kk?{nwgPe3pm߱Cad2)22R&ImZ$m۾]-5S֭݃l֖mrU%11>-yxIuU{.o-iIҚum),*lV>Sum0 y/;$!zL%i͚p*mn[NڱC{]٣oܨQSk־}eꡱ*>Tߋ2_[iٽzT&qO5v:uhvɓu%1Z?Ic?А*[7hL;4rX7io׬u]ٖ)^W*((XLfY%SpTRTT#{0D( eڴn'x}#Iz;nW~A/@{3~j:Or8Zŗꐘݫ]z5]:t(SKЉ'j>߭ߠVc())~g0Ɣi߾_7]ueISNkY,^/WG{lzzj{6Ybr:a*\.{p:pnpTvu֭/#f;^yyWQUn/???W*???YVY,`6݃drC \.֭_/*osLa~oTB6ڱsvMq @0WQQQ;e%޻i0`o#g` L@0 SL) H;B9ع;E WWSpmٰ"ơ|L)S` )@0@@V\T0\ge&Y~ )00@0y**,PXDe2RIqN=D6\TTX2ى&YAԱÙP=u9A*8yBag=z)Ya?y B0=?⋊P^^^?~\6M3fhWRR^zI۷Wpp7nnA7o>JJJܹs}F&!&&ӫW/=S ,AI^^~mM4Oncǎ?ٳgM6w}\s=>3L6cǎ,1 W;^[&]tE0Obcc5uT=C _o1cZl p_ʽ n]דj޼}]̙޽{uJKK#m۶ VBB.\1]RR^~eC֭֮]C&MR-QF]v%:ϯm+))I!!!jӦMLqɤ'|R$n_O<14k,kĉ9r/̙3w^]}պTZZ'N_|dl6kϞ=|A8p@7oVjjvUi1c_W5?_wvZwK/E>Czw^͘1Upc8RT5jӁtR 2t: av(..6 |#//1=jdff3k۷o76ollذXz1m4 Aׯ6^0nVr;v4{=0 c_oaϘ>}yt=a_n̙3>??0LƏ?X􅅅F``Ȑdنl6F+0-ZdtŐd\yƲet~ذlFjjjI^xCSO=i6:uOf۷o7f믿іW7 UXoL6Xzacۍ4c޽ƾ}LѣFNNgFqqQZZjvpNz54i^|E 6LSL̙3+رcZh֭[cǎ)==]^`ETUMw^9Nuݣmev-0ԳgOgvCdX4tP :Tׯ׬Y4p@͚5KE󫮦5_u5KLLT>}ԡC 0@_Էo_~dPF#ɤ93ߦ׳;C%%%9rt5ל1N~~zkzԹsg/v9N9J.0 edd:~xm+`1}huJ:uiM8?6|iMW],k-_\2d  h{u(3Sah=JuPf^߰BL/ EO<,XbTeggW_U>}ԤI[n-ɤ5kT:}V˗z=7o@Wmժ9_/5)_A޽5|]V:x ?2Rڮ]qX @>&NUVn+%%E7bAA^-^Xiiiڷo-ZSjĈ>ͯoY_g8qB˖-zAu*yUWӚf9997o뮻2[o~X jհaN%r5vX۷O7ĉգG3\BB^yϲ4sLy_]w7n*vuU]vUHHH7|PϢuQ5UUrssdM8Q'NPBB/^OjSJBCBh 7H\8_ tEu0QM&i쾕.}NnPiiv֭[u_F6"aЙjҤ 7.}ioj-~8.xOӦz|d=w ӄ-mٰF70{\uMxZjbqf=T8/[nU֭ P.JƏw纏r\ɑͲf;Pá TZZvڥ޽{+++K'Oc=Fa)-zL/PzFEa)\^o\SW/_qX!o^&;1@S6m yf_?hҥz_H+WwfKn4Hjެ#ԯ[ҭ0 mJM4m Ne- E ]+.w~E46Vw6P(i_꣏>oՒ%K.+..K/._~ѻ~믿iӦ);;[kkR'OzVh=jx=nk=mO:u_֕+Wt*..N%%%vH:o 9>Um}NjMzL/pmڴQPPv,##CmڴQXXS>"##C-Zp=2??_6mR^^feq;v'|rjju饗m۶׿88l0W|ڴiqQWY]̙u omxykMשS'Ueo:WmV㧜x[nQQ~'U> ﯒bv˯N0 Cere= NSCv]C[n -j\~'ܹSr `dqE]rϧc7˥#Y T@`ne%v`9sխ[7jOVU=fPv*dP>p:q\. Ж-[HQ4(A ֱÙ*.*ql\*.*T,:͛uVY,o^۷([Xrs9&Yu)i}Q>}(/ 0pxP>S` )@0 AOmٰ"4`j2<^ꨮC8/(V~/S|hJu ,>0.pZhKؤ@Z Ye0lDqUUpqS_/\$[ Q]8*+VYԗTLo)+}Y.?bc6R_LZRzM.N2aMn!U[6R)L֤`ZYrj!g @0IV*7'ofKҪiYU89[7,VkZYY@+RoL^0,Ϫ­LkpYh- ՅӲ` pSo[@מ*iD/* P'_$Ld>~H/!dC/>Bn7J`aX ,3Xގ-2m\@``ii$IX|>_җP( cMM-HGaM `i Ƥ/5Kt 6T/vݹs.3 3>>ZZZ*Ͳ&  BieYuehr&}55-nicX̥eBL&+..1ش2,oAT,;8TA|)ʝ LC9H{AR2H:`A^0`{=z s-7o&zfU=su])bj?bܹ{xt\.б/Ϟ֊eKs ?Yɴ {Lѱ_]˵jh4 #7[owEEk9IrXm&SuU[o|.kʕ?oSJ !yz;x77n|#?1} Oz+W,k?#&'h&4s0w~y!AoB ~ !ndVBRW[zj߽r&)3O^fѫswM)M R^6EEy9!jIJ Kv4,_:PJ;:JK2L܁tZBȐex!BN]hCHUUVtvuwtvqbQyB(V b*()̚U+ !i#W2i$HDPrJh>u{~?yu||")(+z~_fO6U[SS[ScIyrqһo|]?_ɏ Q!,DFw~4_<ԉ4,=\}7==W}IJEQ  װsvͪ|}׎'z!Dܱ~a\B;i߿juZ~nScc{GGRf>^O- EQ87oo{z{_[V,hƒ{BHY?+?{W_?z%OO[=1Jy_˕$U&e9#&uظ(9p4 Egc 0=@ꖒtDO/Ɲ`\~[bg/kzNmD/Xx$BHMjօ*kzu(?|tIu-;ݕKMDτK wZZscd'` zD0!U ʚ/c1>>~OoN'uZݪ+?}u]0;w~r;vYl)`y=cc.{/>`XH[xuyrÏ>vܫVx?wQS^_wzJGh8 ߘ >;?}_ϓBy(RJ/HrYe٩I7udpYm6BHYq(/ $I{7_QJ{~$kdGB,Öiϛ!:‚ƒ~So_qQO3'ѳz*B{!^AMYNrGNy==Rlv7]_?7~Rwnd7~毂g6b1_o\mryyJ$I?ӏ^yE2 MeeBȫr`p_[oϓBȚի~kO>N#t۷?(/77^wOoMuի?z%O_+5U=}ɚ< 2 ?{BHY?+◦ŏ0=@` zD0=@` zD0=@` zD0=@` zD0=@` zD0=@` zD0=@` zD0=@` zD0=@` zD0=@` zD0=@` zD0=@` zD0=@` zD0=@` zD0=@`@ƺ`XmVfM>$IrL.+ Lz]^˺d= $IcッAyhx5\./).*+++/--..N= $I桡޾~כ|~y`|\^UYP_YQ z?qA[{GL~gWwgWJj[)/// 3[w޻@.vߺ}֮..*J`@Ov]ݒ$m$++7_WECA7(޽wk~M3-YaZ\Φ lye`p܅6$B$JٺysmM5r z Ihr3GXr:]NllbV |[|t:Orhº;:GGGޅ?07Rd=dlMXm~qo_B S z[__'x< 8zXѓ;:80&+~$= p'[vyAc#k7Z֬f] …˝G]q]UKc6?yJ6x!\|u fޓE)yz^er\F p&'6DNS_hrK5O"l"S}:DT*jk*+ʍFcR9~y200r^ OzJѓM.\<66>W,[ZYQ|STUVl޴q``ν{sku;u~M62% ~:!u] vҕm۶l.5}wXcc/\ e&aQccJOl:22-'>3zꪪ936>zvGgw8JA< 'k(ݻDQ|,, uܸ&w=]]m]#;d6}q}{> 'JJ6_wչjbb+yyeeC^^f| vf4Sq40YP=YfՊcccIk! Ą8(sr\>xsii`O݃Y Wٹʊ4㙴FG&vۓKMG`x  'ponu!)Y =YI&߻p~fg!@dOD>'OjV@oqO]Rʺ7 |q8g^Bd=Juk>_V% }Dx`]*u-P(n~3 z曺ښoⲥKX/|RJFyBC bۖ/[(U/~vד;!;_H3oiڭ7}.Y<Фx?⢢/@@(>:i5[lٴdJ=YEEM u ǟ~>i'8>? zjkjkGO_@= P>B \& R.O概D<$p6z g7\ Z=gBcH !} DdBLYP=A> 2 g@@h4O:h2'Dd"F>&Ӆ˗U$2Vysġᱡھm[wҐLẞd˨:W;2|GWt;6"ElG/ODں,Jr{'Ν>u[(AOrDy>;P8A??ub*wBu[;CBH@}yr*wBuFkka'8m|zujuy|;I{'!D!K*Kkkj |[ IDATHITYFn\Qg\}J\L.5uu=Dɽlx"Fn%(rN$A俼p>UǑʊ/S8elt PuJ_UQIyeOqKh[WGexw|$ݮ}nuu+,}4zj󍕩H DOY\?b1[L3wV[$mRL[3I2ع~wbK7n $BS nvE#![,;FW` y9U|W't5˖uv* I Gѓ|T $ 'UxVʍQ$ãmݽ=D ,Y$xw;^ ?n(&:)Y :]8ŏDTbQ+̒vUiJ%\\1o,[Tnd Y=qXR`,)Wvܞ:r!>[aJ*G)~nu~+ WBkP8ٴ%DR$Qם֒QJNYI~!}yI@ zIWىZ&qpGRQGOR8.yn($@$mI$s V7$IK}?>>\XV3+87R.S(H\דLDӇ\k26b: tڦ>JZ.8]EyԷ](sV}A{'LJ= {!,1\V<wۭC^4nsx.D[&_5q.jʊ%'H$yS&T99/sT}CV'zɡsرP*i4zHp?e8dq:'Xf^~j*K /w=fJ&hpqeѓ_L5D ?L1fu>|&K "!H p%RQa*ϑM4.ݯ/~BV.䏮(j\BqM0G-kVD;)Ey9+2&q93p'.Na'(n1[,##6ހ(Q*:mVP~ޱ1)) VSg4!_[./+)~vb|4 Cq% ]}u9><;::t:!rrr JpK]iCZw:rWkm|
20GO?ڱcW3֮]϶ _op` zD0=@` zD0=@` ,$O{5[wR! E1鎞;]?@׽4/-ITk䔊+7&D9Yإ5zzTRMbS;ri I~Gʐ2Kp [:aUx r񴱺t-!^x怩W E6y[;]NU]2Ih!R.]a?rںem(%LoN?_?xPs9*U6(Q>੍+ՄC8Ri2mKv-ޭV|;zl cab/,0)yc4itW*Ks V/3buݻs}O899N$ą z9gN={rUܦlv3[!߻_$g} Uu)Bh[WǓ۶k߸uŗ)xh[gruN;C\}, ˶+GF76B$FXz56yMQO;WS8yp Yʛ''s "}F/DX3]x|cCڢRBȃm 'Ξ6xY(;}>|˨-!r'~WX^g¾Seg޴QFqc pϺHפo14Nmji)'Jdlb"e\aADĤ{lgڍ2K>b&DPkuVа<z`t$B" laQw"E gY7TH.cYD$''78\pKGÌC"/B EEڊ_f`J4E ]m#B\@h쨪],>WZ2WAm:u̟6pxxnF3pҩC۷+3Qv;pRB$B$\vrHdM'E\ŀMÝŬ4]s̓}D DذkZynn\TSEdrB:'/%Sk !v'bJM(/ 1Ϡ-!rDA;%F-=(&i:ƟٻC9tCxrؤ:2mSuPJ )ԫ/5_y\/c'ҵ+ɭv9s^W6N}qX**jb\csB3iGEs *uI>-BMuӭ恎F60cC]MS3]yM ~z&b~^ip"JT["-7g}Î^ؗJ%]zzΎ2C^QWU+v/Hͭ&m(!uή͛D۶4lc߯T eŋ9qZR\Y1=sٔ mzOVھqJ[RS]+7\lm\}!bY\z폥EO kvU䨰{ /{rI.昋4|tŠG#v`# 5VWܼZYUGپrs#bEim;EJ 7^q?Q*U},2jK4P+fg^('RIF?;v9sjZ4$ُׄؾ AWԕ,J8zׯ7Fb>9'GhR_V*>w!f,Kò^Z/)Tpg/^JIuY/KAoҌSF/ѓj k˟IyM^D̎m KoW CBB[pdKOp8j+Y '[i"!J_/ۜ_ x6]!uR[QO??kDN'DgfHϟ<Bxyo<1 $ @FTBKs(m=vHv0M$"-"6:v:rT'Yy@^OvD"STSm=r(m=vHzM$"-+ɑsreN^3&7 $ @Ad0M$RKSm=I(R[]#hKOPNXa^o@ !nwl簎BuQz3EZDsuPB$v?؄( t:ϡ0(Bz#lun.!B;xD?8фm>ե(!ܵ[ѮRIgfm3F^*Z[/E<"תe[7?yEO8H$:A}z"5'%ls ӹsɳ7O|yŧ=ҧ>0uI ӴOgYլbҒ`@bk'0lNk)/S* P)y2<.%+ Y SIjuŔu lHGmɉs2RmX4ju;O5ЛG~b+'"ξyD]_U_ߴ 7۵y\Zt #N69qVW󤮺ᵯ[erll4+s*޹R<#kV{F e,\g"wyJ9)JJ|lpSeee7^{cP<,ouqjy<q^?Si%Wk!CmV7uݬ,T*B3+ ߠ>R=ڇ ۜ?8B=xBdo"Jr̀l`U+dJ6jta>mqvME>@<^L%].9Y-c˗O۲)78~6w(qg\3Ovݫ_:57̏DA}z"7J1sH2}mE1Ju= uM-w;Gkf~.9*)!D>x@hgUU-Kwn0ZscQ7(JJ*o߻]Q\$r0鑺E^/~{ⵛ *M^I&gOl]:L[Wm#rzzvq o۲u'ݸN5Q1lxڇԜqIio_[Q*BZnčWpOJEUEuǑhgQa._4j咦#]Q\! >[V_(Rf0t>h)3+|Ьsvq {oXQZqN({c]&j~V;!Ri֍83mƸj7H{mMJqwq5̏2aG'맭K1sHRM*^?}ue5R(~&h̘ya~b߬ui[Ȅ9i7qF>?TRǮ "A dr?>_t>vc( _d;xJwמp/1n_g%K;{λ2_#לǏop܆>Ue]'&™e'|xV8p~HOaN sܑpgLX%- lp:Xp|}fW`f  x{fK,@n,nr, 物\ ~_lA\9`61$|VpN^S%z " | IB ^cc18@ 2 h{F57JT(jX9K.[%$YDTiK*H)ETפYEVmkJ-Uk[ĵҴZlsVg~g`đF2QG}>3Oeꬳ;J p,]uwؤ[vٺnrrNW/l~ưꇉ`t"@\ :f!gKI"^!y('D9s9gϸ}ݦ Bb C&:tsgg.uLfԄ=e8uƳ{uNT il^Wp?ݿT;(guh` ɕqh;'*Bd%pcq݃o,6iגcw G_s(Nqoa5KmNYD$Q||yYvi?Y6Xsw];ݻU:w]iڷ f8I8~S˧>GK"!$]P1Bэ&VnCT]].u1$]njK Ub^  )橵JY(HVFG[F;sxac )9n̕:q yxph<\Ruz ֛'v)%rUY"dv  tm`&K M8,ia -=+Gr2퍇= ?!))s׶na(Rbh1P&th^#3Yp-ۛ䊂Lœj lD^ŧtNjKGlbw` F8o;CQTb {d!ڭ'^[ /Q/uV(eZ+15b$D&|ALC5*8&Mzg[9 2H3$}Yѥ$ȹ=?P:SOڂ)2֨ʴ.xO ,|k }Bv]&oFgt(sVċ\"YJ<@`R$3h紋 #ɱK55!W120g4#k@L(ct0*@5h $$%[1N@1pތvf?t&v$KpBвdꙋ}[t5vvVN'Bflj)j mx|ɺ<+#d8Cd̙26͙I YmR\2)Kvk#\@w@Vbd^"=-[#wjZC!vk@75r¾;-L$;ip\ǎn%F:"_M]:)f&@:IgML4`sk^!e40߹m{*Aݖy]:ѡ(m} k25uc2(@DКOL׭ay4=c5iOW`!4xԢMղۈ a4t##Sih\l:AuTzgHF\!3PI΄oKd*9M̳6jz!CW A'Pih&?|գǽ'O KkhJjP-Э?I* ns(.:)U{k4s&{˖97$|~YFf!VIAVSjpG=עp۹yq!js4pb R|b9MXEM Qpq[;8NBQvc>qV~}{-f=I^IQF.Ь;4O3p IzŎm⺭){0dȦJAB7[g뭵 K]oC`Dk>3~rtbKGD07 pHYs.#.#x?vtIME90>7XIDATxw|e3 @H"zz" (*ȩ?O== EAEiB &HI0dؒlA_yNyf}}r:w4MaE7 ]Gu<nǃvrpݸ\n222ׯ/ Bc̘$''pر8lv;͆ݷ4 UT5hQ% BqAD8AAACAAD8AACAAD8AAACAAAACAAD8AAACAD8As\lǖM "Saj[6Q6A) U;gW  ! p "   ! " \ڝ$ |} ޹Cݻi,l1s|'HNnzu+MƖtEQb.Ҧ]2vcqqq~.DUUvT4"0LybB4?[̛マrt؞ePlYnvz( tU N vyb`Na{@C^*23xb`ʗ/(=OtZlS?O^o/6;s,`̇:ER>5 SrcƏgFx76zӥS'TU7ߊ9K^iϾ? i֤q#:kfp}=Ծ=UGrRf͝LJ'c0nԈɛs1RB9LyWkIߜSwq0MS*󈴪:8NV]ϋ'>bD^|MV}UЯ2ccF+k^GSr )Div99fePԩS3rnSDC,E_.i)t=9!:+խ-Xa<Ѓ4=aAjr2׃Ӧ'2i)L1J=;J+l"6Goy qQiI(7б}L0خ-MgsFF̕~Rn*1x.& #p\Lt܁Nmo7rzvr5hӪol(yuU. =,͛6t:ԪYS MWRJfM{-o\[uu}6{-Y+DETp8ڡaڵ͟0i &Mwfl!Cc*~9*V(PT%WEE,^ԫ[>dɲ46L.\F[gF J5ƒ0DiZJ B o 2i8ТY3TU T a`:'M.d`I P w}\"B`\8$$ p qU <ɸywk[M.A`<fHnSV2GgR'7gУg={g^nAl?A^y r0L/,'33y7P8xWԿ˫U#}f9 MBݷU<(bo[ }o<>8I\8TDʔS:E&|j5}Ln^76'3+!CSBy8Akes2/s]wrSlܴ2_.\nc ԫS^kVi]i3gcmn.;o8y~9xڵ՗_1Ѓo7 o3;~^?. ?cv|wjO ȗf_f Zhի Sz5ƍY3ЯN ؃1tȳ̞:UQs擹UTqcx핗oCmʱd@U 6)r6 1!Gz=yԧWO07#G2s<>=9Ӧo~VYB֩Sڿ;}>ӤnHO+–[!c ͚6i?~g0L4{ 2eh~>7}W+;%g>>r)'~S>kYN([Zw:]#b V&7`]g˖vԪY-wuND86el%R%:kC˦7R7:8ϽwuiTٶsʗK8|tV^M-0M͛ժ5b&7`o >EUpܜMMs)OWh"08zuju|^J>벐҈5_#.h=OjV,#R4MSJT^j8;t+?emnnAvm nR-ԫF^$:֚ϖ,gSvRɷ6VK\֬\K.L xֺ5vˌs/Q;#FqW灇rr^xv0׃i\}Uһz@kiv;C W6ohݲ%~=76Nբy3N眦RJapڵж-xgȂEg%젧2l8˯!qoti/a,]x|OHa;p9{퀂i(BQlvG`6 41|o͎2tTEQm4L<7il4 3{ C10T+ XF4D@A +ȷcXW53`$kR8VrPpʏg(,`q&" oV:X eC "tnE/ E)tQp @!@B$ĝ X GAxhVWoC\XVep1QVE G$7U|qT-*ڀ Bdlu(E>V# FXGAzC& (f@@#" (L@)*!֋qD`#$~ū" @<0X@ÎXa wO F4⡄Y~1R|Õl=e%"" EoaiաEi+(L< o]b&֣Jk*Au2^)F$.gcyX䎊dUFA(/J :.R:6+(L<|QVHaY (pnģ8PT * m~UhnD4A΍x%Y90 mEY$ڋAΏ%ra)шGp6H$A΍Q\A)>Q*vAA Hq?4`B4b!!P2DٗRIENDB`libhandy-0.0.13/doc/images/view-switcher.png000066400000000000000000000247471360136463700207340ustar00rootroot00000000000000PNG  IHDRM!E=zTXtRaw profile type exifxڭmr EkYAXIUACIc?{%Y!_֯C ʧ>_z>nWKs:!Ϻ߯>_5==W\*Ǔ6|Q ýbwqO s¯3_}[KD-u'xR~uW =LPs:0$-I\lYI)Hc3VI%Kv,hV ,ǏXێ?U<3 Ƅ'\bhCsLx(K.|i&g v5^kFB5JE3MyYgݣ#g/^y_JV^eVpmNvev@E+jߑ{E-@}5KeB b/fJJ w0 -j(r$ږ>#noq?!w] ;n?[:UHNQHz!iކ4dCCv3pm|hsi7fj|˭zovv<'Bp-Ű OV\6@eYܫ)wݛ&=]\6ƨDp,;zZ e6v,&kL@^:d@ՉSg; 7EklKr96aK$_ڨj[PTPkG*cD17}tm^geWuyAaܒʎ: -qV9e镊Ͷm8[=MS0=}F|PB[]csc5'uA0uڙKT/u2]O5l"{Z~Rhos=)p09mm&)Cz[~ xxmkuN5G3_\~vHphdeWFrz^x8H:QFL}M|O[kq/!a}Ed.FIqfF]sCHjMnw>=nO)B()^SB Zցl# u26~/IarP3!j1Nw7}3àr$&лB(t0xM\}ԊQ(_O:  Bٱ[ LfU&3sy=|GxctkCec`Si2!0-Nxfs#.S"ݑ}^d2Q:ƌXbΈa\XSws` ,Aka)ڎKffpj??|^ul@XKbhGJp,٣ U/pĺ *H$,!:h ]F~&D9<9WEAt`if!u+l]68z^FBH1RqK(4U"ڼiE1 `ưC~i`` 3 tU2󤥙=0NW!E`P*k% +]r}*U2jr𤶯_4SmVьIpL Z}7G.:9[}t ]žѓhf)5g`*K[uL1CNw?qA]AHti=e>fFyH!\hѵ>iCCPICC profilex}=H@_[KE*vꐡ:Yp*BZu0 4$).kŪ "%/)=FfjN&lnU"A0L}NS_.γ9z@ՅMwPj%, !B(+D g4/k_NG<F!BM0PX  +((`0|Ϫ+4r4MGGG/U,0 Bqƪji?uE|iO` tsu3ß;Vs7&>>hX`E+4 bUL*7WIRf~M&г%b4QvmHLl=)g2k)/%B!MVTRRRhݦ 1ўͨ((~rG1jr=}CbEPX,d>C|T%w,+S6B3(DEGӵk7l26[MԠiwst $F!QUUeegdp6reo)MO !8ӯUr,qPRb!^ _%99ؘ`0*ānb6>)-)nɞ1) !dЀZۨkCu7,ĊDLfh*!Q!mhJbӦfnwbr0 { Ijki-!BSZZBrRQU߬Zf,MJPEϒ%Bqi;yg4ߐʀPŨgA-K",j*vD:yMʯBKu9ݙLS˿yq{e`#?$hi8N4U묐(B&d44m>aQ9Q[Y|b𾉺61y!!9Q!8]1D6S 4EC ~NgNo$}nS^XB!8<}˳Z~*Pr4̉oh9o]B!8wf6s\Duϩ<2&PE ] Bjnfnt&X!DU-uݧ>\NQB3.URWyAAv mbI4 powlqֻQPwec0bM "\U}EqR,q 0|51z6u,[?-NQ#ILHXn.s>Ǟ45h Æp襁xpZm *_\)" ˅iDFFzp80 DDDFACOX\k5&{}2p8,^ 3ADEEF9U4Vc0_?_>Xt &6믽^w|W\^Mӥ,$>ʫXV^0 9f /LyX&O'aQm]U"0Xoɰz&'u`P5Z9jFni8vg{}ʦ-[bggywԿ/x oKg/۹jeg%|ϿЬY"1Bf}tҙF;og _A5:q(..N`č7пOS 3`oZm=lX[gɨrNoגwz1$nBۤ6( &a8u|zK r|`k220eƿ s:&#mڠ $j?h&sQ1fׁ(xzEQ|o]EZ}kju̪uٓwC;w̿{_J:N8xw||"nGxRnq3s3F7%hc1"S{=J4+RM͗_ŨKTl%g?>ʫܽ7tԁDžnĮ(85f-2Ts5}čq9leCNt=|xyRR]{O2k"B n\uiyEng͆,_'$z{x2П~{trJ\zKjCCJϠ]r{ЯwOVȮ=8EsJVR/6W c혋iӲ%wF%'p}31L\s7?%%|ٗܳ# M9ԧ-SSRYDFFh62k)#﹏9}wz|>A}WU56҄q1i4vQʒ+5شe491#nAyf{zK\7d0W&A1u_%des}]ޗ忓_P@x{C̅ tl巻HMMyPq9y>9?.Y sMqpt]۪՘-tu' MxX~עs''滳p:]<ģt?OsƻaɯѡMMcҴ7̳d,Y A+97ӬimZW<4Mcp9dt:ҮMKo-hƏK;vAu%Ct-wd2Ѣy ?=o?wf@V2M& 3ߡE]WҭkW^:RxD8lپ{ShҤ ImT[vE߳zF^~y}4W_M'srs;I|9tfr#Aw ߶kwy5'EF <㹘K;}>3>;we3^J%p&/LSq(#͚S?3d^; ;( aTwPܛiѼ͚&'%Mؽ׷bl۹./dҸyG麡8].~ugQL4If#ojӡ3wCc͆Ms)Nиnlشů7z%Bs1ߥ$'%Pwa\gm LB8VF]׹햛2)D햛ٗ֟3?6-GeN5Ϗ˞TCI۶y7Hs쌾o5b tt!.ٵ/;nb!1ttÀp:Ֆ@p:Y)EsҞD nP5={GNLVNN)?}9V sUcrS5;㹸eGibU{$lQ3{bzF&y׈4򗝏NF^Dᯚoj V)B3|5 rnkH=p0|&sŀ~#g֭hǾq,'uܑ%@zШaZ<LwdΞ`ڧW'de{_~=5Mvѳe{1[,s䩱dˡAy? q(*2|ź xљ}diVQVY4,d9Dsĉa+`T|OH6[ݺ%/-[:΍rpX̅ޕ+`_j]d}ws͕ot,ݏ-QCԪZJr,g> y.:7;w}صy)ܽٶc'?2:-7/A&p:<9eDGEjv|޽X^ڻWU9sru'aw8B:wURRb~L&J-f"O51ԦT{@+ɠ}Կ/6m>mN}_k vʵy&.KΚ<嗝fjM?чT~ЃjEs|cp PVf#/?M00srM 4k[jeko=mnzv7f1\֯t#Z);QZ[=m*QQ1dIi +n/#?/')Rf-yBcƍ'&Uk7 jCy kD DW386&]io{:Ы%Y^=SPd&uKNGeTa?նd) PgO7{V}=Úqs2 d=|RkjB_Nѣ>@zF'Ociyjp߭v?}z]rĚ 7'/];/F=E'nsnflܲs@o3SQXe@,cvOhɣNCZ/t՚ CnQ29v4g7E]Z2x_E;a_2{ 9 8T>fKW^2C;萕N~nNaSs7"::g\͛:{S5]Թ#N6ƘhڷMromquD%uA2f}_'~=^uE5DDDy~ZJТY"e/Z,;Æ( }Ͼ`w1 шlltY>^x4lЀcFM&PRZW!//ְAYnfM&/?k3"]sL4MH`mDG'8NœД./nޗ_iV,bv!ee6ArR'M'Ò_))) Lh AC&'ڔɨ v=㹸\^2I7hhe4*g]׸qۭdw<0o|BҬ +Wᬙ.;BS].;ΚɆ+N9 b}nM<UTUUq\8N\.Ӊtp8پ};7pf EQA>1ΈuYEᆡW#N2k) {<{i*)Ajoזi&ܘ4h(`_&Hb`Ks`++0? 7cPҒb[PZRg+BZ}S{CM9 }N-T|Vfz551"EQp9}~ 5iJddeR ( (me =AO7\;_n03ӰqSkАq 8vO-Eue': 4:tPUu,kkM~1-hn|{7t,EaEEGӸIb<'6b˟:7 ^}4Ub,#عC< ]id2#L?F#(qRI\OîpOH^О﹛.gѸQ#.z»E k# ]QRVfȉ3Ŗ#ssBle#rA:aaU;c~0 t}4%Vj1a1Oue':FA~^e-HF;:|P}UU1/T6-;wU`qfM [o`ѣ[W"1hnZZo:y9ٲV%/8G ܊Z>=owDY)^ue9,Aԑ(g!WJ!j// @!BHPB!!QBHB!(WY!IQ!(WY!IQ!㤏B!(B!$( !B B!pro#=)j9)B! R(B!$( !BНԦgSdlQQeq3IDATIB!Z1j !BL,B!$( !B B!BB!(B!$( !B B!BB!(B!$( !B B!BB!(B!E!BsWNa IENDB`libhandy-0.0.13/doc/meson.build000066400000000000000000000041301360136463700163030ustar00rootroot00000000000000if get_option('gtk_doc') subdir('xml') private_headers = [ 'config.h', 'gtkprogresstrackerprivate.h', 'gtk-window-private.h', 'hdy-animation-private.h', 'hdy-main-private.h', 'hdy-keypad-button-private.h', 'hdy-paginator-box-private.h', 'hdy-preferences-group-private.h', 'hdy-preferences-page-private.h', 'hdy-shadow-helper-private.h', 'hdy-style-private.h', 'hdy-swipe-tracker-private.h', 'hdy-swipeable-private.h', 'hdy-view-switcher-button-private.h', ] images = [ 'images/arrows.png', 'images/dialer.png', 'images/dialog-desktop.png', 'images/dialog-mobile.png', 'images/header-bar.png', 'images/keypad.png', 'images/list.png', 'images/preferences-window.png', 'images/search.png', 'images/view-switcher.png', 'images/view-switcher-bar.png', ] content_files = [ 'build-howto.xml', 'visual-index.xml', ] glib_prefix = dependency('glib-2.0').get_pkgconfig_variable('prefix') glib_docpath = glib_prefix / 'share' / 'gtk-doc' / 'html' docpath = get_option('datadir') / 'gtk-doc' / 'html' gnome.gtkdoc('libhandy', main_xml: 'handy-docs.xml', src_dir: [ meson.source_root() / 'src', meson.build_root() / 'src', ], dependencies: libhandy_dep, gobject_typesfile: 'libhandy.types', scan_args: [ '--rebuild-types', '--ignore-headers=' + ' '.join(private_headers), ], fixxref_args: [ '--html-dir=@0@'.format(docpath), '--extra-dir=@0@'.format(glib_docpath / 'glib'), '--extra-dir=@0@'.format(glib_docpath / 'gobject'), '--extra-dir=@0@'.format(glib_docpath / 'gio'), '--extra-dir=@0@'.format(glib_docpath / 'gi'), '--extra-dir=@0@'.format(glib_docpath / 'gtk3'), ], install_dir: 'libhandy', content_files: content_files, html_assets: images, install: installable) endif libhandy-0.0.13/doc/visual-index.xml000066400000000000000000000045571360136463700173100ustar00rootroot00000000000000 %gtkdocentities; ]> Visual index 3 Widgets in &package_string;Widget overview. Widgets HdyDialog - on mobile and desktop HdyViewSwitcher libhandy-0.0.13/doc/xml/000077500000000000000000000000001360136463700147435ustar00rootroot00000000000000libhandy-0.0.13/doc/xml/gtkdocentities.ent.in000066400000000000000000000006131360136463700211000ustar00rootroot00000000000000 libhandy-0.0.13/doc/xml/meson.build000066400000000000000000000011671360136463700171120ustar00rootroot00000000000000ent_conf = configuration_data() ent_conf.set('PACKAGE', 'Handy') ent_conf.set('PACKAGE_BUGREPORT', 'https://source.puri.sm/Librem5/libhandy/issues') ent_conf.set('PACKAGE_NAME', 'Handy') ent_conf.set('PACKAGE_STRING', 'libhandy') ent_conf.set('PACKAGE_TARNAME', 'libhandy-' + meson.project_version()) ent_conf.set('PACKAGE_URL', 'https://source.puri.sm/Librem5/libhandy') ent_conf.set('PACKAGE_VERSION', meson.project_version()) ent_conf.set('PACKAGE_API_VERSION', apiversion) ent_conf.set('PACKAGE_API_NAME', package_api_name) configure_file(input: 'gtkdocentities.ent.in', output: 'gtkdocentities.ent', configuration: ent_conf) libhandy-0.0.13/examples/000077500000000000000000000000001360136463700152145ustar00rootroot00000000000000libhandy-0.0.13/examples/example.py000077500000000000000000000010311360136463700172170ustar00rootroot00000000000000#!/usr/bin/python3 import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk gi.require_version('Handy', '0.0') from gi.repository import Handy import sys def print_number(dialer, number): print("Dial {}".format(number)) def quit(dialer, number=None): Gtk.main_quit() window = Gtk.Window(title="Dialer Example with Python") dialer = Handy.Dialer() dialer.connect("submitted", print_number) dialer.connect("submitted", quit) window.connect("destroy", quit) window.add(dialer) window.show_all() Gtk.main() libhandy-0.0.13/examples/handy-demo.c000066400000000000000000000036051360136463700174110ustar00rootroot00000000000000#include #define HANDY_USE_UNSTABLE_API #include #include "hdy-demo-preferences-window.h" #include "hdy-demo-window.h" static void show_preferences (GSimpleAction *action, GVariant *state, gpointer user_data) { GtkApplication *app = GTK_APPLICATION (user_data); GtkWindow *window = gtk_application_get_active_window (app); HdyDemoPreferencesWindow *preferences = hdy_demo_preferences_window_new (); gtk_window_set_transient_for (GTK_WINDOW (preferences), window); gtk_widget_show (GTK_WIDGET (preferences)); } static void startup (GtkApplication *app) { GtkCssProvider *css_provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (css_provider, "/sm/puri/handy/demo/ui/style.css"); gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), GTK_STYLE_PROVIDER (css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref (css_provider); } static void show_window (GtkApplication *app) { HdyDemoWindow *window; window = hdy_demo_window_new (app); gtk_widget_show (GTK_WIDGET (window)); } int main (int argc, char **argv) { GtkApplication *app; int status; static GActionEntry app_entries[] = { { "preferences", show_preferences, NULL, NULL, NULL }, }; hdy_init (&argc, &argv); app = gtk_application_new ("sm.puri.Handy.Demo", G_APPLICATION_FLAGS_NONE); g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app); g_signal_connect (app, "startup", G_CALLBACK (startup), NULL); g_signal_connect (app, "activate", G_CALLBACK (show_window), NULL); status = g_application_run (G_APPLICATION (app), argc, argv); g_object_unref (app); return status; } libhandy-0.0.13/examples/handy-demo.gresources.xml000066400000000000000000000007251360136463700221470ustar00rootroot00000000000000 hdy-demo-preferences-window.ui hdy-demo-window.ui hdy-dialog-complex-example.ui hdy-view-switcher-demo-window.ui style.css libhandy-0.0.13/examples/hdy-demo-preferences-window.c000066400000000000000000000013701360136463700226730ustar00rootroot00000000000000#include "hdy-demo-preferences-window.h" struct _HdyDemoPreferencesWindow { HdyPreferencesWindow parent_instance; }; G_DEFINE_TYPE (HdyDemoPreferencesWindow, hdy_demo_preferences_window, HDY_TYPE_PREFERENCES_WINDOW) HdyDemoPreferencesWindow * hdy_demo_preferences_window_new (void) { return g_object_new (HDY_TYPE_DEMO_PREFERENCES_WINDOW, NULL); } static void hdy_demo_preferences_window_class_init (HdyDemoPreferencesWindowClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/demo/ui/hdy-demo-preferences-window.ui"); } static void hdy_demo_preferences_window_init (HdyDemoPreferencesWindow *self) { gtk_widget_init_template (GTK_WIDGET (self)); } libhandy-0.0.13/examples/hdy-demo-preferences-window.h000066400000000000000000000005641360136463700227040ustar00rootroot00000000000000#pragma once #define HANDY_USE_UNSTABLE_API #include G_BEGIN_DECLS #define HDY_TYPE_DEMO_PREFERENCES_WINDOW (hdy_demo_preferences_window_get_type()) G_DECLARE_FINAL_TYPE (HdyDemoPreferencesWindow, hdy_demo_preferences_window, HDY, DEMO_PREFERENCES_WINDOW, HdyPreferencesWindow) HdyDemoPreferencesWindow *hdy_demo_preferences_window_new (void); G_END_DECLS libhandy-0.0.13/examples/hdy-demo-preferences-window.ui000066400000000000000000000150171360136463700230710ustar00rootroot00000000000000 libhandy-0.0.13/examples/hdy-demo-window.c000066400000000000000000000403741360136463700204030ustar00rootroot00000000000000#include "hdy-demo-window.h" #include #define HANDY_USE_UNSTABLE_API #include #include "hdy-view-switcher-demo-window.h" struct _HdyDemoWindow { GtkApplicationWindow parent_instance; HdyLeaflet *header_box; HdyLeaflet *content_box; GtkButton *back; GtkToggleButton *search_button; GtkStackSidebar *sidebar; GtkStack *stack; HdyComboRow *leaflet_transition_row; GtkWidget *box_keypad; GtkListBox *keypad_listbox; HdyKeypad *keypad; HdySearchBar *search_bar; GtkEntry *search_entry; GtkListBox *column_listbox; GtkListBox *lists_listbox; HdyComboRow *combo_row; HdyComboRow *enum_combo_row; HdyHeaderGroup *header_group; HdyPaginator *paginator; GtkListBox *paginator_listbox; HdyComboRow *paginator_orientation_row; HdyComboRow *paginator_indicator_style_row; }; G_DEFINE_TYPE (HdyDemoWindow, hdy_demo_window, GTK_TYPE_APPLICATION_WINDOW) static gboolean hdy_demo_window_key_pressed_cb (GtkWidget *sender, GdkEvent *event, HdyDemoWindow *self) { GdkModifierType default_modifiers = gtk_accelerator_get_default_mod_mask (); guint keyval; GdkModifierType state; gdk_event_get_keyval (event, &keyval); gdk_event_get_state (event, &state); if ((keyval == GDK_KEY_q || keyval == GDK_KEY_Q) && (state & default_modifiers) == GDK_CONTROL_MASK) { gtk_widget_destroy (GTK_WIDGET (self)); return TRUE; } return FALSE; } static void update (HdyDemoWindow *self) { GtkWidget *header_child = hdy_leaflet_get_visible_child (self->header_box); HdyFold fold = hdy_leaflet_get_fold (self->header_box); g_assert (header_child == NULL || GTK_IS_HEADER_BAR (header_child)); hdy_header_group_set_focus (self->header_group, fold == HDY_FOLD_FOLDED ? GTK_HEADER_BAR (header_child) : NULL); } static void update_header_bar (HdyDemoWindow *self) { const gchar *visible_child_name; visible_child_name = gtk_stack_get_visible_child_name (GTK_STACK (self->stack)); gtk_widget_set_visible (GTK_WIDGET (self->search_button), g_str_equal (visible_child_name, "search-bar")); } static void hdy_demo_window_notify_header_visible_child_cb (GObject *sender, GParamSpec *pspec, HdyDemoWindow *self) { update (self); } static void hdy_demo_window_notify_fold_cb (GObject *sender, GParamSpec *pspec, HdyDemoWindow *self) { update (self); } static void update_leaflet_swipe (HdyDemoWindow *self) { gboolean first_page = (hdy_paginator_get_position (self->paginator) <= 0); gboolean paginator_visible = (gtk_stack_get_visible_child (self->stack) == GTK_WIDGET (self->paginator)); hdy_leaflet_set_can_swipe_back (self->content_box, !paginator_visible || first_page); } static void hdy_demo_window_notify_visible_child_cb (GObject *sender, GParamSpec *pspec, HdyDemoWindow *self) { hdy_leaflet_set_visible_child_name (self->content_box, "content"); update_header_bar (self); update_leaflet_swipe (self); } static void hdy_demo_window_back_clicked_cb (GtkWidget *sender, HdyDemoWindow *self) { hdy_leaflet_set_visible_child_name (self->content_box, "sidebar"); } static gchar * leaflet_transition_name (HdyEnumValueObject *value, gpointer user_data) { g_return_val_if_fail (HDY_IS_ENUM_VALUE_OBJECT (value), NULL); switch (hdy_enum_value_object_get_value (value)) { case HDY_LEAFLET_TRANSITION_TYPE_NONE: return g_strdup (_("None")); case HDY_LEAFLET_TRANSITION_TYPE_SLIDE: return g_strdup (_("Slide")); case HDY_LEAFLET_TRANSITION_TYPE_OVER: return g_strdup (_("Over")); case HDY_LEAFLET_TRANSITION_TYPE_UNDER: return g_strdup (_("Under")); default: return NULL; } } static void notify_leaflet_transition_cb (GObject *sender, GParamSpec *pspec, HdyDemoWindow *self) { HdyComboRow *row = HDY_COMBO_ROW (sender); g_assert (HDY_IS_COMBO_ROW (row)); g_assert (HDY_IS_DEMO_WINDOW (self)); hdy_leaflet_set_transition_type (HDY_LEAFLET (self->content_box), hdy_combo_row_get_selected_index (row)); } static void dialog_close_cb (GtkDialog *self) { gtk_widget_destroy (GTK_WIDGET (self)); } static void dialog_clicked_cb (GtkButton *btn, HdyDemoWindow *self) { GtkWidget *dlg; GtkWidget *lbl; dlg = hdy_dialog_new (GTK_WINDOW (self)); gtk_window_set_title (GTK_WINDOW (dlg), "HdyDialog"); lbl = gtk_label_new ("Hello, World!"); g_object_set (lbl, "margin", 12, NULL); gtk_widget_set_vexpand (lbl, TRUE); gtk_widget_set_valign (lbl, GTK_ALIGN_CENTER); gtk_widget_set_halign (lbl, GTK_ALIGN_CENTER); gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), lbl); gtk_widget_show (lbl); gtk_widget_show (dlg); } static void dialog_action_clicked_cb (GtkButton *btn, HdyDemoWindow *self) { GtkWidget *dlg; GtkWidget *lbl; dlg = hdy_dialog_new (GTK_WINDOW (self)); gtk_window_set_title (GTK_WINDOW (dlg), "HdyDialog"); gtk_dialog_add_buttons (GTK_DIALOG (dlg), "Done", GTK_RESPONSE_ACCEPT, "Cancel", GTK_RESPONSE_CANCEL, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_ACCEPT); g_signal_connect (G_OBJECT (dlg), "response", G_CALLBACK (dialog_close_cb), NULL); lbl = gtk_label_new ("Hello, World!"); g_object_set (lbl, "margin", 12, NULL); gtk_widget_set_vexpand (lbl, TRUE); gtk_widget_set_valign (lbl, GTK_ALIGN_CENTER); gtk_widget_set_halign (lbl, GTK_ALIGN_CENTER); gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), lbl); gtk_widget_show (lbl); gtk_widget_show (dlg); } static void dialog_complex_deeper_clicked_cb (GtkStack *stack) { gtk_stack_set_visible_child_name (stack, "sub"); } static void dialog_complex_back_clicked_cb (GtkStack *stack) { gtk_stack_set_visible_child_name (stack, "main"); } static void dialog_complex_clicked_cb (GtkButton *btn, HdyDemoWindow *self) { g_autoptr (GtkBuilder) builder = gtk_builder_new_from_resource ("/sm/puri/handy/demo/ui/hdy-dialog-complex-example.ui"); GtkWidget *dlg, *back, *deeper, *stack; dlg = GTK_WIDGET (gtk_builder_get_object (builder, "dialog")); back = GTK_WIDGET (gtk_builder_get_object (builder, "back")); deeper = GTK_WIDGET (gtk_builder_get_object (builder, "deeper")); stack = GTK_WIDGET (gtk_builder_get_object (builder, "content_stack")); g_signal_connect_swapped (deeper, "clicked", G_CALLBACK (dialog_complex_deeper_clicked_cb), stack); g_signal_connect_swapped (back, "clicked", G_CALLBACK (dialog_complex_back_clicked_cb), stack); gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (self)); gtk_widget_show (dlg); } static void view_switcher_demo_clicked_cb (GtkButton *btn, HdyDemoWindow *self) { HdyViewSwitcherDemoWindow *window = hdy_view_switcher_demo_window_new (); gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (self)); gtk_widget_show (GTK_WIDGET (window)); } static gchar * paginator_orientation_name (HdyEnumValueObject *value, gpointer user_data) { g_return_val_if_fail (HDY_IS_ENUM_VALUE_OBJECT (value), NULL); switch (hdy_enum_value_object_get_value (value)) { case GTK_ORIENTATION_HORIZONTAL: return g_strdup (_("Horizontal")); case GTK_ORIENTATION_VERTICAL: return g_strdup (_("Vertical")); default: return NULL; } } static void notify_paginator_position_cb (GObject *sender, GParamSpec *pspec, HdyDemoWindow *self) { update_leaflet_swipe (self); } static void notify_paginator_orientation_cb (GObject *sender, GParamSpec *pspec, HdyDemoWindow *self) { HdyComboRow *row = HDY_COMBO_ROW (sender); gboolean horizontal; g_assert (HDY_IS_COMBO_ROW (row)); g_assert (HDY_IS_DEMO_WINDOW (self)); horizontal = (hdy_combo_row_get_selected_index (row) == GTK_ORIENTATION_HORIZONTAL); g_object_set (self->paginator, "orientation", hdy_combo_row_get_selected_index (row), "margin-top", horizontal ? 6 : 0, "margin-bottom", horizontal ? 6 : 0, "margin-left", horizontal ? 0 : 6, "margin-right", horizontal ? 0 : 6, NULL); } static gchar * paginator_indicator_style_name (HdyEnumValueObject *value, gpointer user_data) { g_return_val_if_fail (HDY_IS_ENUM_VALUE_OBJECT (value), NULL); switch (hdy_enum_value_object_get_value (value)) { case HDY_PAGINATOR_INDICATOR_STYLE_NONE: return g_strdup (_("None")); case HDY_PAGINATOR_INDICATOR_STYLE_DOTS: return g_strdup (_("Dots")); case HDY_PAGINATOR_INDICATOR_STYLE_LINES: return g_strdup (_("Lines")); default: return NULL; } } static void notify_paginator_indicator_style_cb (GObject *sender, GParamSpec *pspec, HdyDemoWindow *self) { HdyComboRow *row = HDY_COMBO_ROW (sender); g_assert (HDY_IS_COMBO_ROW (row)); g_assert (HDY_IS_DEMO_WINDOW (self)); hdy_paginator_set_indicator_style (self->paginator, hdy_combo_row_get_selected_index (row)); } static void paginator_return_clicked_cb (GtkButton *btn, HdyDemoWindow *self) { g_autoptr (GList) children; children = gtk_container_get_children (GTK_CONTAINER (self->paginator)); hdy_paginator_scroll_to (self->paginator, GTK_WIDGET (children->data)); } HdyDemoWindow * hdy_demo_window_new (GtkApplication *application) { return g_object_new (HDY_TYPE_DEMO_WINDOW, "application", application, NULL); } static void hdy_demo_window_constructed (GObject *object) { HdyDemoWindow *self = HDY_DEMO_WINDOW (object); G_OBJECT_CLASS (hdy_demo_window_parent_class)->constructed (object); hdy_search_bar_connect_entry (self->search_bar, self->search_entry); } static void hdy_demo_window_class_init (HdyDemoWindowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = hdy_demo_window_constructed; gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/demo/ui/hdy-demo-window.ui"); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, header_box); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, content_box); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, back); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, search_button); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, sidebar); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, stack); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, leaflet_transition_row); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, box_keypad); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, keypad_listbox); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, keypad); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, search_bar); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, search_entry); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, column_listbox); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, lists_listbox); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, combo_row); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, enum_combo_row); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, header_group); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, paginator); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, paginator_listbox); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, paginator_orientation_row); gtk_widget_class_bind_template_child (widget_class, HdyDemoWindow, paginator_indicator_style_row); gtk_widget_class_bind_template_callback_full (widget_class, "key_pressed_cb", G_CALLBACK(hdy_demo_window_key_pressed_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "notify_header_visible_child_cb", G_CALLBACK(hdy_demo_window_notify_header_visible_child_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "notify_fold_cb", G_CALLBACK(hdy_demo_window_notify_fold_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "notify_visible_child_cb", G_CALLBACK(hdy_demo_window_notify_visible_child_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "back_clicked_cb", G_CALLBACK(hdy_demo_window_back_clicked_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "notify_leaflet_transition_cb", G_CALLBACK(notify_leaflet_transition_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "dialog_clicked_cb", G_CALLBACK(dialog_clicked_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "dialog_action_clicked_cb", G_CALLBACK(dialog_action_clicked_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "dialog_complex_clicked_cb", G_CALLBACK(dialog_complex_clicked_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "view_switcher_demo_clicked_cb", G_CALLBACK(view_switcher_demo_clicked_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "notify_paginator_position_cb", G_CALLBACK(notify_paginator_position_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "notify_paginator_orientation_cb", G_CALLBACK(notify_paginator_orientation_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "notify_paginator_indicator_style_cb", G_CALLBACK(notify_paginator_indicator_style_cb)); gtk_widget_class_bind_template_callback_full (widget_class, "paginator_return_clicked_cb", G_CALLBACK(paginator_return_clicked_cb)); } static void lists_page_init (HdyDemoWindow *self) { GListStore *list_store; HdyValueObject *obj; gtk_list_box_set_header_func (self->lists_listbox, hdy_list_box_separator_header, NULL, NULL); list_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT); obj = hdy_value_object_new_string ("Foo"); g_list_store_insert (list_store, 0, obj); g_clear_object (&obj); obj = hdy_value_object_new_string ("Bar"); g_list_store_insert (list_store, 1, obj); g_clear_object (&obj); obj = hdy_value_object_new_string ("Baz"); g_list_store_insert (list_store, 2, obj); g_clear_object (&obj); hdy_combo_row_bind_name_model (self->combo_row, G_LIST_MODEL (list_store), (HdyComboRowGetNameFunc) hdy_value_object_dup_string, NULL, NULL); hdy_combo_row_set_for_enum (self->enum_combo_row, GTK_TYPE_LICENSE, hdy_enum_value_row_name, NULL, NULL); } static void hdy_demo_window_init (HdyDemoWindow *self) { gtk_widget_init_template (GTK_WIDGET (self)); hdy_combo_row_set_for_enum (self->leaflet_transition_row, HDY_TYPE_LEAFLET_TRANSITION_TYPE, leaflet_transition_name, NULL, NULL); hdy_combo_row_set_selected_index (self->leaflet_transition_row, HDY_LEAFLET_TRANSITION_TYPE_OVER); gtk_list_box_set_header_func (self->column_listbox, hdy_list_box_separator_header, NULL, NULL); gtk_list_box_set_header_func (self->keypad_listbox, hdy_list_box_separator_header, NULL, NULL); lists_page_init (self); gtk_list_box_set_header_func (self->paginator_listbox, hdy_list_box_separator_header, NULL, NULL); hdy_combo_row_set_for_enum (self->paginator_orientation_row, GTK_TYPE_ORIENTATION, paginator_orientation_name, NULL, NULL); hdy_combo_row_set_for_enum (self->paginator_indicator_style_row, HDY_TYPE_PAGINATOR_INDICATOR_STYLE, paginator_indicator_style_name, NULL, NULL); hdy_combo_row_set_selected_index (self->paginator_indicator_style_row, HDY_PAGINATOR_INDICATOR_STYLE_DOTS); hdy_leaflet_set_visible_child_name (self->content_box, "content"); update_header_bar (self); } libhandy-0.0.13/examples/hdy-demo-window.h000066400000000000000000000004341360136463700204010ustar00rootroot00000000000000#pragma once #include G_BEGIN_DECLS #define HDY_TYPE_DEMO_WINDOW (hdy_demo_window_get_type()) G_DECLARE_FINAL_TYPE (HdyDemoWindow, hdy_demo_window, HDY, DEMO_WINDOW, GtkApplicationWindow) HdyDemoWindow *hdy_demo_window_new (GtkApplication *application); G_END_DECLS libhandy-0.0.13/examples/hdy-demo-window.ui000066400000000000000000002515161360136463700206000ustar00rootroot00000000000000 True 6 18 vertical 200 app.preferences True Preferences True horizontal horizontal 0 10000 600 100 10 0 10000 500 100 10 libhandy-0.0.13/examples/hdy-dialog-complex-example.ui000066400000000000000000000120061360136463700226710ustar00rootroot00000000000000 False Handy Demo True True slide-left-right True False We need to go deeper True main True True False False center True True Back True False go-previous-symbolic 1 sub True 0 True False True slide-left-right True False Go deeper center center main True False 0.5 center 12 That's too deep! center True sub libhandy-0.0.13/examples/hdy-view-switcher-demo-window.c000066400000000000000000000057001360136463700231730ustar00rootroot00000000000000#include "hdy-view-switcher-demo-window.h" #include #define HANDY_USE_UNSTABLE_API #include struct _HdyViewSwitcherDemoWindow { GtkWindow parent_instance; HdySqueezer *squeezer; HdyViewSwitcherBar *switcher_bar; GtkLabel *title_label; HdyViewSwitcher *title_narrow_switcher; HdyViewSwitcher *title_wide_switcher; }; G_DEFINE_TYPE (HdyViewSwitcherDemoWindow, hdy_view_switcher_demo_window, GTK_TYPE_WINDOW) static gboolean is_title_label_visible (GBinding *binding, const GValue *from_value, GValue *to_value, gpointer user_data) { g_value_set_boolean (to_value, g_value_get_object (from_value) == user_data); return TRUE; } static void hdy_view_switcher_demo_window_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { HdyViewSwitcherDemoWindow *self = HDY_VIEW_SWITCHER_DEMO_WINDOW (widget); hdy_squeezer_set_child_enabled (self->squeezer, GTK_WIDGET (self->title_wide_switcher), allocation->width > 600); hdy_squeezer_set_child_enabled (self->squeezer, GTK_WIDGET (self->title_narrow_switcher), allocation->width > 400); GTK_WIDGET_CLASS (hdy_view_switcher_demo_window_parent_class)->size_allocate (widget, allocation); } static void hdy_view_switcher_demo_window_class_init (HdyViewSwitcherDemoWindowClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); widget_class->size_allocate = hdy_view_switcher_demo_window_size_allocate; gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/demo/ui/hdy-view-switcher-demo-window.ui"); gtk_widget_class_bind_template_child (widget_class, HdyViewSwitcherDemoWindow, squeezer); gtk_widget_class_bind_template_child (widget_class, HdyViewSwitcherDemoWindow, switcher_bar); gtk_widget_class_bind_template_child (widget_class, HdyViewSwitcherDemoWindow, title_label); gtk_widget_class_bind_template_child (widget_class, HdyViewSwitcherDemoWindow, title_narrow_switcher); gtk_widget_class_bind_template_child (widget_class, HdyViewSwitcherDemoWindow, title_wide_switcher); } static void hdy_view_switcher_demo_window_init (HdyViewSwitcherDemoWindow *self) { gtk_widget_init_template (GTK_WIDGET (self)); g_object_bind_property_full (self->squeezer, "visible-child", self->switcher_bar, "reveal", G_BINDING_SYNC_CREATE, is_title_label_visible, NULL, self->title_label, NULL); } HdyViewSwitcherDemoWindow * hdy_view_switcher_demo_window_new (void) { return g_object_new (HDY_TYPE_VIEW_SWITCHER_DEMO_WINDOW, NULL); } libhandy-0.0.13/examples/hdy-view-switcher-demo-window.h000066400000000000000000000005301360136463700231740ustar00rootroot00000000000000#pragma once #include G_BEGIN_DECLS #define HDY_TYPE_VIEW_SWITCHER_DEMO_WINDOW (hdy_view_switcher_demo_window_get_type()) G_DECLARE_FINAL_TYPE (HdyViewSwitcherDemoWindow, hdy_view_switcher_demo_window, HDY, VIEW_SWITCHER_DEMO_WINDOW, GtkWindow) HdyViewSwitcherDemoWindow *hdy_view_switcher_demo_window_new (void); G_END_DECLS libhandy-0.0.13/examples/hdy-view-switcher-demo-window.ui000066400000000000000000000133771360136463700233770ustar00rootroot00000000000000 libhandy-0.0.13/examples/meson.build000066400000000000000000000010001360136463700173450ustar00rootroot00000000000000if get_option('examples') handy_demo_resources = gnome.compile_resources( 'handy-demo-resources', 'handy-demo.gresources.xml', c_name: 'hdy', ) handy_demo_sources = [ handy_demo_resources, 'handy-demo.c', 'hdy-demo-preferences-window.c', 'hdy-demo-window.c', 'hdy-view-switcher-demo-window.c', libhandy_generated_headers, ] handy_demo = executable('handy-@0@-demo'.format(apiversion), handy_demo_sources, dependencies: libhandy_dep, gui_app: true, install: installable, ) endif libhandy-0.0.13/examples/sm.puri.Handy.Demo.json000066400000000000000000000015311360136463700214310ustar00rootroot00000000000000{ "app-id": "sm.puri.Handy.Demo", "runtime": "org.gnome.Platform", "runtime-version": "master", "sdk": "org.gnome.Sdk", "command": "handy-0.0-demo", "finish-args": [ "--env=DCONF_USER_CONFIG_DIR=.config/dconf", "--share=ipc", "--socket=x11", "--socket=wayland", "--device=all" ], "build-options": { "cflags": "-O2 -g", "cxxflags": "-O2 -g", "env": { "V": "1" } }, "modules": [ { "name": "handy-demo", "buildsystem": "meson", "builddir": true, "build-options": "examples", "config-opts": [ "-Dglade_catalog=disabled", "-Dintrospection=disabled", "-Dtests=false", "-Dvapi=false" ], "sources": [ { "type": "git", "url": "https://source.puri.sm/Librem5/libhandy.git" } ] } ] } libhandy-0.0.13/examples/style.css000066400000000000000000000002361360136463700170670ustar00rootroot00000000000000stacksidebar list { border-left-width: 0px; border-right-width: 0px; } hdypaginator.horizontal .paginator-icon { -gtk-icon-transform: rotate(90deg); } libhandy-0.0.13/glade/000077500000000000000000000000001360136463700144525ustar00rootroot00000000000000libhandy-0.0.13/glade/glade-catalog.dtd000066400000000000000000000152601360136463700176370ustar00rootroot00000000000000 libhandy-0.0.13/glade/glade-hdy-header-group.c000066400000000000000000000121411360136463700210330ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ * * Based on * glade-gtk-size-group.c - GladeWidgetAdaptor for GtkSizeGroup * Copyright (C) 2013 Tristan Van Berkom */ #include "glade-hdy-header-group.h" #include #include #include #define GLADE_TAG_HEADERGROUP_WIDGETS "headerbars" #define GLADE_TAG_HEADERGROUP_WIDGET "headerbar" static void glade_hdy_header_group_read_widgets (GladeWidget *widget, GladeXmlNode *node) { GladeXmlNode *widgets_node; GladeProperty *property; gchar *string = NULL; if ((widgets_node = glade_xml_search_child (node, GLADE_TAG_HEADERGROUP_WIDGETS)) != NULL) { GladeXmlNode *n; for (n = glade_xml_node_get_children (widgets_node); n; n = glade_xml_node_next (n)) { gchar *widget_name, *tmp; if (!glade_xml_node_verify (n, GLADE_TAG_HEADERGROUP_WIDGET)) continue; widget_name = glade_xml_get_property_string_required (n, GLADE_TAG_NAME, NULL); if (string == NULL) { string = widget_name; } else if (widget_name != NULL) { tmp = g_strdup_printf ("%s%s%s", string, GPC_OBJECT_DELIMITER, widget_name); string = (g_free (string), tmp); g_free (widget_name); } } } if (string) { property = glade_widget_get_property (widget, "headerbars"); g_assert (property); /* we must synchronize this directly after loading this project * (i.e. lookup the actual objects after they've been parsed and * are present). */ g_object_set_data_full (G_OBJECT (property), "glade-loaded-object", string, g_free); } } void glade_hdy_header_group_read_widget (GladeWidgetAdaptor *adaptor, GladeWidget *widget, GladeXmlNode *node) { if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) || glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE))) return; /* First chain up and read in all the normal properties.. */ GWA_GET_CLASS (G_TYPE_OBJECT)->read_widget (adaptor, widget, node); glade_hdy_header_group_read_widgets (widget, node); } static void glade_hdy_header_group_write_widgets (GladeWidget *widget, GladeXmlContext *context, GladeXmlNode *node) { GladeXmlNode *widgets_node, *widget_node; GList *widgets = NULL, *list; GladeWidget *awidget; widgets_node = glade_xml_node_new (context, GLADE_TAG_HEADERGROUP_WIDGETS); if (glade_widget_property_get (widget, "headerbars", &widgets)) { for (list = widgets; list; list = list->next) { awidget = glade_widget_get_from_gobject (list->data); widget_node = glade_xml_node_new (context, GLADE_TAG_HEADERGROUP_WIDGET); glade_xml_node_append_child (widgets_node, widget_node); glade_xml_node_set_property_string (widget_node, GLADE_TAG_NAME, glade_widget_get_name (awidget)); } } if (!glade_xml_node_get_children (widgets_node)) glade_xml_node_delete (widgets_node); else glade_xml_node_append_child (node, widgets_node); } void glade_hdy_header_group_write_widget (GladeWidgetAdaptor *adaptor, GladeWidget *widget, GladeXmlContext *context, GladeXmlNode *node) { if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) || glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE))) return; /* First chain up and read in all the normal properties.. */ GWA_GET_CLASS (G_TYPE_OBJECT)->write_widget (adaptor, widget, context, node); glade_hdy_header_group_write_widgets (widget, context, node); } void glade_hdy_header_group_set_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *property_name, const GValue *value) { if (!strcmp (property_name, "headerbars")) { GSList *sg_widgets, *slist; GList *widgets, *list; /* remove old widgets */ if ((sg_widgets = hdy_header_group_get_header_bars (HDY_HEADER_GROUP (object))) != NULL) { /* copy since we are modifying an internal list */ sg_widgets = g_slist_copy (sg_widgets); for (slist = sg_widgets; slist; slist = slist->next) hdy_header_group_remove_header_bar (HDY_HEADER_GROUP (object), GTK_HEADER_BAR (slist->data)); g_slist_free (sg_widgets); } /* add new widgets */ if ((widgets = g_value_get_boxed (value)) != NULL) { for (list = widgets; list; list = list->next) hdy_header_group_add_header_bar (HDY_HEADER_GROUP (object), GTK_HEADER_BAR (list->data)); } } else { GWA_GET_CLASS (G_TYPE_OBJECT)->set_property (adaptor, object, property_name, value); } } libhandy-0.0.13/glade/glade-hdy-header-group.h000066400000000000000000000016731360136463700210500ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define HANDY_USE_UNSTABLE_API #include void glade_hdy_header_group_set_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *property_name, const GValue *value); void glade_hdy_header_group_write_widget (GladeWidgetAdaptor *adaptor, GladeWidget *widget, GladeXmlContext *context, GladeXmlNode *node); void glade_hdy_header_group_read_widget (GladeWidgetAdaptor *adaptor, GladeWidget *widget, GladeXmlNode *node); libhandy-0.0.13/glade/glade-hdy-paginator.c000066400000000000000000000340531360136463700204430ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ * * Based on * glade-gtk-stack.c - GladeWidgetAdaptor for GtkStack * Copyright (C) 2014 Red Hat, Inc. */ #include "glade-hdy-paginator.h" #include #include #include #include #define CENTER_CONTENT_INSENSITIVE_MSG _("This property does not apply unless Show Indicators is set.") static gint hdy_paginator_get_page (HdyPaginator *paginator) { return round (hdy_paginator_get_position (paginator)); } static gboolean hdy_paginator_is_transient (HdyPaginator *paginator) { return fmod (hdy_paginator_get_position (paginator), 1.0) > 0.00001; } static gint get_n_pages_excluding_placeholders (GtkContainer *container) { GList *children, *l; gint n_pages; children = gtk_container_get_children (container); n_pages = 0; for (l = children; l; l = l->next) if (!GLADE_IS_PLACEHOLDER (l->data)) n_pages++; g_list_free (children); return n_pages; } static gint get_page_index (GtkContainer *container, GtkWidget *child) { GList *children; gint index; children = gtk_container_get_children (container); index = g_list_index (children, child); g_list_free (children); return index; } static GtkWidget * get_nth_page (GtkContainer *container, gint n) { GList *children; GtkWidget *child; children = gtk_container_get_children (container); child = g_list_nth_data (children, n); g_list_free (children); return child; } static void selection_changed_cb (GladeProject *project, GladeWidget *gwidget) { GList *list; GtkWidget *page, *sel_widget; GtkContainer *container; GList *children, *l; gint index; list = glade_project_selection_get (project); if (!list || g_list_length (list) != 1) return; sel_widget = list->data; container = GTK_CONTAINER (glade_widget_get_object (gwidget)); if (!GTK_IS_WIDGET (sel_widget) || !gtk_widget_is_ancestor (sel_widget, GTK_WIDGET (container))) return; children = gtk_container_get_children (container); for (l = children; l; l = l->next) { page = l->data; if (sel_widget == page || gtk_widget_is_ancestor (sel_widget, page)) { hdy_paginator_scroll_to (HDY_PAGINATOR (container), page); index = get_page_index (container, page); glade_widget_property_set (gwidget, "page", index); break; } } g_list_free (children); } static void project_changed_cb (GladeWidget *gwidget, GParamSpec *pspec, gpointer user_data) { GladeProject *project, *old_project; project = glade_widget_get_project (gwidget); old_project = g_object_get_data (G_OBJECT (gwidget), "paginator-project-ptr"); if (old_project) g_signal_handlers_disconnect_by_func (G_OBJECT (old_project), G_CALLBACK (selection_changed_cb), gwidget); if (project) g_signal_connect (G_OBJECT (project), "selection-changed", G_CALLBACK (selection_changed_cb), gwidget); g_object_set_data (G_OBJECT (gwidget), "paginator-project-ptr", project); } static void position_changed_cb (HdyPaginator *paginator, GParamSpec *pspec, GladeWidget *gwidget) { gint old_page, new_page; glade_widget_property_get (gwidget, "page", &old_page); new_page = hdy_paginator_get_page (paginator); if (old_page == new_page || hdy_paginator_is_transient (paginator)) return; glade_widget_property_set (gwidget, "page", new_page); } void glade_hdy_paginator_post_create (GladeWidgetAdaptor *adaptor, GObject *container, GladeCreateReason reason) { GladeWidget *gwidget = glade_widget_get_from_gobject (container); if (reason == GLADE_CREATE_USER) gtk_container_add (GTK_CONTAINER (container), glade_placeholder_new ()); g_signal_connect (G_OBJECT (gwidget), "notify::project", G_CALLBACK (project_changed_cb), NULL); project_changed_cb (gwidget, NULL, NULL); g_signal_connect (G_OBJECT (container), "notify::position", G_CALLBACK (position_changed_cb), gwidget); glade_widget_property_set_sensitive (gwidget, "indicator-spacing", FALSE, CENTER_CONTENT_INSENSITIVE_MSG); glade_widget_property_set_sensitive (gwidget, "center-content", FALSE, CENTER_CONTENT_INSENSITIVE_MSG); } void glade_hdy_paginator_child_action_activate (GladeWidgetAdaptor *adaptor, GObject *container, GObject *object, const gchar *action_path) { if (!strcmp (action_path, "insert_page_after") || !strcmp (action_path, "insert_page_before")) { GladeWidget *parent; GladeProperty *property; GtkWidget *placeholder; gint pages, index; parent = glade_widget_get_from_gobject (container); glade_widget_property_get (parent, "pages", &pages); glade_command_push_group (_("Insert placeholder to %s"), glade_widget_get_name (parent)); index = get_page_index (GTK_CONTAINER (container), GTK_WIDGET (object)); if (!strcmp (action_path, "insert_page_after")) index++; placeholder = glade_placeholder_new (); hdy_paginator_insert (HDY_PAGINATOR (container), placeholder, index); hdy_paginator_scroll_to (HDY_PAGINATOR (container), placeholder); property = glade_widget_get_property (parent, "pages"); glade_command_set_property (property, pages + 1); property = glade_widget_get_property (parent, "page"); glade_command_pop_group (); } else if (strcmp (action_path, "remove_page") == 0) { GladeWidget *parent; GladeProperty *property; gint pages, position; parent = glade_widget_get_from_gobject (container); glade_widget_property_get (parent, "pages", &pages); glade_command_push_group (_("Remove placeholder from %s"), glade_widget_get_name (parent)); g_assert (GLADE_IS_PLACEHOLDER (object)); gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (object)); property = glade_widget_get_property (parent, "pages"); glade_command_set_property (property, pages - 1); glade_widget_property_get (parent, "page", &position); property = glade_widget_get_property (parent, "page"); glade_command_set_property (property, position); glade_command_pop_group (); } else GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_action_activate (adaptor, container, object, action_path); } static void set_n_pages (GObject *container, const GValue *value) { GladeWidget *gbox; GtkWidget *child; gint old_size, new_size, i, page; new_size = g_value_get_int (value); old_size = hdy_paginator_get_n_pages (HDY_PAGINATOR (container)); if (old_size == new_size) return; for (i = old_size; i < new_size; i++) gtk_container_add (GTK_CONTAINER (container), glade_placeholder_new ()); for (i = old_size; i > 0; i--) { if (old_size <= new_size) break; child = get_nth_page (GTK_CONTAINER (container), i - 1); if (GLADE_IS_PLACEHOLDER (child)) { gtk_container_remove (GTK_CONTAINER (container), child); old_size--; } } gbox = glade_widget_get_from_gobject (container); glade_widget_property_get (gbox, "page", &page); glade_widget_property_set (gbox, "page", page); } static void set_page (GObject *object, const GValue *value) { gint new_page; GtkWidget *child; new_page = g_value_get_int (value); child = get_nth_page (GTK_CONTAINER (object), new_page); if (child) hdy_paginator_scroll_to (HDY_PAGINATOR (object), child); } static void set_indicator_style (GObject *container, const GValue *value) { GladeWidget *gwidget; HdyPaginatorIndicatorStyle style; gwidget = glade_widget_get_from_gobject (container); style = g_value_get_enum (value); glade_widget_property_set_sensitive (gwidget, "indicator-spacing", style != HDY_PAGINATOR_INDICATOR_STYLE_NONE, CENTER_CONTENT_INSENSITIVE_MSG); glade_widget_property_set_sensitive (gwidget, "center-content", style != HDY_PAGINATOR_INDICATOR_STYLE_NONE, CENTER_CONTENT_INSENSITIVE_MSG); } void glade_hdy_paginator_set_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *id, const GValue *value) { if (!strcmp (id, "pages")) set_n_pages (object, value); else if (!strcmp (id, "page")) set_page (object, value); else { if (!strcmp (id, "indicator-style")) set_indicator_style (object, value); GWA_GET_CLASS (GTK_TYPE_CONTAINER)->set_property (adaptor, object, id, value); } } void glade_hdy_paginator_get_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *id, GValue *value) { if (!strcmp (id, "pages")) { g_value_reset (value); g_value_set_int (value, hdy_paginator_get_n_pages (HDY_PAGINATOR (object))); } else if (!strcmp (id, "page")) { g_value_reset (value); g_value_set_int (value, hdy_paginator_get_page (HDY_PAGINATOR (object))); } else { GWA_GET_CLASS (GTK_TYPE_CONTAINER)->get_property (adaptor, object, id, value); } } static gboolean glade_hdy_paginator_verify_n_pages (GObject *object, const GValue *value) { gint new_size, old_size; new_size = g_value_get_int (value); old_size = get_n_pages_excluding_placeholders (GTK_CONTAINER (object)); return old_size <= new_size; } static gboolean glade_hdy_paginator_verify_page (GObject *object, const GValue *value) { gint page, pages; page = g_value_get_int (value); pages = hdy_paginator_get_n_pages (HDY_PAGINATOR (object)); return 0 <= page && page < pages; } gboolean glade_hdy_paginator_verify_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *id, const GValue *value) { if (!strcmp (id, "pages")) return glade_hdy_paginator_verify_n_pages (object, value); else if (!strcmp (id, "page")) return glade_hdy_paginator_verify_page (object, value); else if (GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property) return GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property (adaptor, object, id, value); return TRUE; } void glade_hdy_paginator_add_child (GladeWidgetAdaptor *adaptor, GObject *container, GObject *child) { GladeWidget *gbox, *gchild; gint pages, page; if (!glade_widget_superuser () && !GLADE_IS_PLACEHOLDER (child)) { GList *l, *children; children = gtk_container_get_children (GTK_CONTAINER (container)); for (l = g_list_last (children); l; l = l->prev) { GtkWidget *widget = l->data; if (GLADE_IS_PLACEHOLDER (widget)) { gtk_container_remove (GTK_CONTAINER (container), widget); break; } } g_list_free (children); } gtk_container_add (GTK_CONTAINER (container), GTK_WIDGET (child)); gchild = glade_widget_get_from_gobject (child); if (gchild) glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE); gbox = glade_widget_get_from_gobject (container); glade_widget_property_get (gbox, "pages", &pages); glade_widget_property_set (gbox, "pages", pages); glade_widget_property_get (gbox, "page", &page); glade_widget_property_set (gbox, "page", page); } void glade_hdy_paginator_remove_child (GladeWidgetAdaptor *adaptor, GObject *container, GObject *child) { GladeWidget *gbox; gint pages, page; gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (child)); gbox = glade_widget_get_from_gobject (container); glade_widget_property_get (gbox, "pages", &pages); glade_widget_property_set (gbox, "pages", pages); glade_widget_property_get (gbox, "page", &page); glade_widget_property_set (gbox, "page", page); } void glade_hdy_paginator_replace_child (GladeWidgetAdaptor *adaptor, GObject *container, GObject *current, GObject *new_widget) { GladeWidget *gbox, *gchild; gint pages, page, index; index = get_page_index (GTK_CONTAINER (container), GTK_WIDGET (current)); gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (current)); hdy_paginator_insert (HDY_PAGINATOR (container), GTK_WIDGET (new_widget), index); hdy_paginator_scroll_to_full (HDY_PAGINATOR (container), GTK_WIDGET (new_widget), 0); gchild = glade_widget_get_from_gobject (new_widget); if (gchild) glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE); /* NOTE: make sure to sync this at the end because new_widget could be * a placeholder and syncing these properties could destroy it. */ gbox = glade_widget_get_from_gobject (container); glade_widget_property_get (gbox, "pages", &pages); glade_widget_property_set (gbox, "pages", pages); glade_widget_property_get (gbox, "page", &page); glade_widget_property_set (gbox, "page", page); } libhandy-0.0.13/glade/glade-hdy-paginator.h000066400000000000000000000044031360136463700204440ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define HANDY_USE_UNSTABLE_API #include void glade_hdy_paginator_post_create (GladeWidgetAdaptor *adaptor, GObject *container, GladeCreateReason reason); void glade_hdy_paginator_child_action_activate (GladeWidgetAdaptor *adaptor, GObject *container, GObject *object, const gchar *action_path); void glade_hdy_paginator_set_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *id, const GValue *value); void glade_hdy_paginator_get_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *id, GValue *value); gboolean glade_hdy_paginator_verify_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *id, const GValue *value); void glade_hdy_paginator_add_child (GladeWidgetAdaptor *adaptor, GObject *container, GObject *child); void glade_hdy_paginator_remove_child (GladeWidgetAdaptor *adaptor, GObject *container, GObject *child); void glade_hdy_paginator_replace_child (GladeWidgetAdaptor *adaptor, GObject *container, GObject *current, GObject *new_widget); libhandy-0.0.13/glade/glade-hdy-swipe-group.c000066400000000000000000000123561360136463700207420ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ * * Based on * hdy-header-group.c - GladeWidgetAdaptor for HdyHeaderGroup * Copyright (C) 2018 Purism SPC * Copyright (C) 2013 Tristan Van Berkom */ #include "glade-hdy-swipe-group.h" #include #include #include #define PROP_SWIPEABLES "swipeables" #define GLADE_TAG_SWIPEGROUP_SWIPEABLES "swipeables" #define GLADE_TAG_SWIPEGROUP_SWIPEABLE "swipeable" static void glade_hdy_swipe_group_read_widgets (GladeWidget *widget, GladeXmlNode *node) { GladeXmlNode *widgets_node; GladeProperty *property; gchar *string = NULL; if ((widgets_node = glade_xml_search_child (node, GLADE_TAG_SWIPEGROUP_SWIPEABLES)) != NULL) { GladeXmlNode *n; for (n = glade_xml_node_get_children (widgets_node); n; n = glade_xml_node_next (n)) { gchar *widget_name, *tmp; if (!glade_xml_node_verify (n, GLADE_TAG_SWIPEGROUP_SWIPEABLE)) continue; widget_name = glade_xml_get_property_string_required (n, GLADE_TAG_NAME, NULL); if (string == NULL) { string = widget_name; } else if (widget_name != NULL) { tmp = g_strdup_printf ("%s%s%s", string, GPC_OBJECT_DELIMITER, widget_name); string = (g_free (string), tmp); g_free (widget_name); } } } if (string) { property = glade_widget_get_property (widget, PROP_SWIPEABLES); g_assert (property); /* we must synchronize this directly after loading this project * (i.e. lookup the actual objects after they've been parsed and * are present). */ g_object_set_data_full (G_OBJECT (property), "glade-loaded-object", string, g_free); } } void glade_hdy_swipe_group_read_widget (GladeWidgetAdaptor *adaptor, GladeWidget *widget, GladeXmlNode *node) { if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) || glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE))) return; /* First chain up and read in all the normal properties.. */ GWA_GET_CLASS (G_TYPE_OBJECT)->read_widget (adaptor, widget, node); glade_hdy_swipe_group_read_widgets (widget, node); } static void glade_hdy_swipe_group_write_widgets (GladeWidget *widget, GladeXmlContext *context, GladeXmlNode *node) { GladeXmlNode *widgets_node, *widget_node; GList *widgets = NULL, *list; GladeWidget *awidget; widgets_node = glade_xml_node_new (context, GLADE_TAG_SWIPEGROUP_SWIPEABLES); if (glade_widget_property_get (widget, PROP_SWIPEABLES, &widgets)) { for (list = widgets; list; list = list->next) { awidget = glade_widget_get_from_gobject (list->data); widget_node = glade_xml_node_new (context, GLADE_TAG_SWIPEGROUP_SWIPEABLE); glade_xml_node_append_child (widgets_node, widget_node); glade_xml_node_set_property_string (widget_node, GLADE_TAG_NAME, glade_widget_get_name (awidget)); } } if (!glade_xml_node_get_children (widgets_node)) glade_xml_node_delete (widgets_node); else glade_xml_node_append_child (node, widgets_node); } void glade_hdy_swipe_group_write_widget (GladeWidgetAdaptor *adaptor, GladeWidget *widget, GladeXmlContext *context, GladeXmlNode *node) { if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) || glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE))) return; /* First chain up and read in all the normal properties.. */ GWA_GET_CLASS (G_TYPE_OBJECT)->write_widget (adaptor, widget, context, node); glade_hdy_swipe_group_write_widgets (widget, context, node); } void glade_hdy_swipe_group_set_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *property_name, const GValue *value) { if (!strcmp (property_name, PROP_SWIPEABLES)) { GSList *sg_widgets, *slist; GList *widgets, *list; /* remove old widgets */ if ((sg_widgets = hdy_swipe_group_get_swipeables (HDY_SWIPE_GROUP (object))) != NULL) { /* copy since we are modifying an internal list */ sg_widgets = g_slist_copy (sg_widgets); for (slist = sg_widgets; slist; slist = slist->next) hdy_swipe_group_remove_swipeable (HDY_SWIPE_GROUP (object), HDY_SWIPEABLE (slist->data)); g_slist_free (sg_widgets); } /* add new widgets */ if ((widgets = g_value_get_boxed (value)) != NULL) { for (list = widgets; list; list = list->next) hdy_swipe_group_add_swipeable (HDY_SWIPE_GROUP (object), HDY_SWIPEABLE (list->data)); } } else { GWA_GET_CLASS (G_TYPE_OBJECT)->set_property (adaptor, object, property_name, value); } } libhandy-0.0.13/glade/glade-hdy-swipe-group.h000066400000000000000000000021001360136463700207310ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ * * Based on * hdy-header-group.h - GladeWidgetAdaptor for HdyHeaderGroup * Copyright (C) 2018 Purism SPC */ #pragma once #include #define HANDY_USE_UNSTABLE_API #include void glade_hdy_swipe_group_set_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *property_name, const GValue *value); void glade_hdy_swipe_group_write_widget (GladeWidgetAdaptor *adaptor, GladeWidget *widget, GladeXmlContext *context, GladeXmlNode *node); void glade_hdy_swipe_group_read_widget (GladeWidgetAdaptor *adaptor, GladeWidget *widget, GladeXmlNode *node); libhandy-0.0.13/glade/libhandy.xml000066400000000000000000000164501360136463700167740ustar00rootroot00000000000000 glade_hdy_header_group_read_widget glade_hdy_header_group_write_widget glade_hdy_header_group_set_property GladeParamObjects GtkHeaderBar List of headerbars in this group glade_hdy_paginator_post_create glade_hdy_paginator_add_child glade_hdy_paginator_remove_child glade_hdy_paginator_replace_child glade_hdy_paginator_child_action_activate glade_hdy_paginator_get_property glade_hdy_paginator_set_property glade_hdy_paginator_verify_property GParamInt 1 The number of pages in the stack GParamInt 0 Set the currently active page to edit, this property will not be saved glade_hdy_swipe_group_read_widget glade_hdy_swipe_group_write_widget glade_hdy_swipe_group_set_property GladeParamObjects HdySwipeable List of widgets in this group libhandy-0.0.13/glade/meson.build000066400000000000000000000027301360136463700166160ustar00rootroot00000000000000if glade_catalog # This is needed to work around this issue in Glade: # https://gitlab.gnome.org/GNOME/glade/issues/379 is_flatpak_app = get_option('prefix') == '/app' glade_xml = 'libhandy.xml' if (is_flatpak_app) module_dir = libdir / 'glade' / 'modules' else module_dir = gladeui_dep.get_pkgconfig_variable('moduledir') endif dtd = meson.current_source_dir() / 'glade-catalog.dtd' if (is_flatpak_app) glade_catalogdir = datadir / 'glade' / 'catalogs' else glade_catalogdir = gladeui_dep.get_pkgconfig_variable('catalogdir') endif libglade_hdy_sources = [ 'glade-hdy-header-group.c', 'glade-hdy-paginator.c', 'glade-hdy-swipe-group.c', ] libglade_hdy_deps = [ libhandy_dep, gladeui_dep, ] libglade_hdy_args = [] # Our custom glade module libglade_hdy = shared_library( 'glade-handy', libglade_hdy_sources, c_args: libglade_hdy_args, dependencies: libglade_hdy_deps, include_directories: [ root_inc, src_inc ], install: installable, install_dir: module_dir, ) # Validate glade catalog xmllint = find_program('xmllint', required: true) if xmllint.found() custom_target( 'xmllint', build_by_default: true, input: glade_xml, output: 'doesnotexist', command: [xmllint, '--dtdvalid', dtd, '--noout', '@INPUT@'], ) endif if installable # Install glade catalog install_data(glade_xml, install_dir: glade_catalogdir) endif endif libhandy-0.0.13/libhandy.doap000066400000000000000000000022321360136463700160340ustar00rootroot00000000000000 libhandy libhandy A library full of GTK widgets for mobile phones libhandy is a collection of GTK widgets for applications targeting mobile phones. The Librem 5 phone drives the development of this project. C Guido Günther Adrien Plazas libhandy-0.0.13/libhandy.syms000066400000000000000000000000631360136463700161040ustar00rootroot00000000000000LIBHANDY_0_0_0 { global: hdy_*; local: *; }; libhandy-0.0.13/meson.build000066400000000000000000000112551360136463700155440ustar00rootroot00000000000000project('libhandy', 'c', version: '0.0.13', license: 'LGPL-2.1+', meson_version: '>= 0.49.0', default_options: [ 'warning_level=1', 'buildtype=debugoptimized', 'c_std=gnu11' ], ) version_arr = meson.project_version().split('.') handy_version_major = version_arr[0].to_int() handy_version_minor = version_arr[1].to_int() handy_version_micro = version_arr[2].to_int() apiversion = '0.0' soversion = 0 package_api_name = '@0@-@1@'.format(meson.project_name(), apiversion) if handy_version_minor.is_odd() handy_interface_age = 0 else handy_interface_age = handy_version_micro endif # maintaining compatibility with libtool versioning # current = minor * 100 + micro - interface # revision = interface current = handy_version_minor * 100 + handy_version_micro - handy_interface_age revision = handy_interface_age libversion = '@0@.@1@.@2@'.format(soversion, current, revision) config_h = configuration_data() config_h.set_quoted('GETTEXT_PACKAGE', 'libhandy') config_h.set_quoted('LOCALEDIR', get_option('prefix') / get_option('localedir')) configure_file( output: 'config.h', configuration: config_h, ) add_project_arguments([ '-DHAVE_CONFIG_H', '-DHANDY_COMPILATION', '-I' + meson.build_root(), ], language: 'c') root_inc = include_directories('.') src_inc = include_directories('src') cc = meson.get_compiler('c') global_c_args = [] test_c_args = [ '-Wcast-align', '-Wdate-time', '-Wdeclaration-after-statement', ['-Werror=format-security', '-Werror=format=2'], '-Wendif-labels', '-Werror=incompatible-pointer-types', '-Werror=missing-declarations', '-Werror=overflow', '-Werror=return-type', '-Werror=shift-count-overflow', '-Werror=shift-overflow=2', '-Werror=implicit-fallthrough=3', '-Wformat-nonliteral', '-Wformat-security', '-Winit-self', '-Wmaybe-uninitialized', '-Wmissing-field-initializers', '-Wmissing-include-dirs', '-Wmissing-noreturn', '-Wnested-externs', '-Wno-missing-field-initializers', '-Wno-sign-compare', '-Wno-strict-aliasing', '-Wno-unused-parameter', '-Wold-style-definition', '-Wpointer-arith', '-Wredundant-decls', '-Wshadow', '-Wstrict-prototypes', '-Wswitch-default', '-Wswitch-enum', '-Wtype-limits', '-Wundef', '-Wunused-function', ] target_system = target_machine.system() if get_option('buildtype') != 'plain' if target_system == 'windows' test_c_args += '-fstack-protector' else test_c_args += '-fstack-protector-strong' endif endif if get_option('profiling') test_c_args += '-pg' endif foreach arg: test_c_args if cc.has_multi_arguments(arg) global_c_args += arg endif endforeach add_project_arguments( global_c_args, language: 'c' ) # Setup various paths that subdirectory meson.build files need package_subdir = get_option('package_subdir') # When used as subproject datadir = get_option('datadir') / package_subdir libdir = get_option('libdir') / package_subdir girdir = get_option('datadir') / package_subdir / 'gir-1.0' typelibdir = get_option('libdir') / package_subdir / 'girepository-1.0' if package_subdir != '' vapidir = get_option('datadir') / package_subdir / 'vapi' else vapidir = get_option('datadir') / 'vala' / 'vapi' endif static = get_option('static') installable = not (static and meson.is_subproject()) glade_catalog_feature = get_option('glade_catalog') if static and glade_catalog_feature.enabled() error('The Glade Catalog isn’t available for static libhandy.') endif gladeui_dep = dependency('gladeui-2.0', required : glade_catalog_feature) glade_catalog = gladeui_dep.found() and not static introspection_feature = get_option('introspection') if static and introspection_feature.enabled() error('Introspection isn’t available for static libhandy.') endif introspection = introspection_feature.enabled() or (introspection_feature.auto() and not static) gnome = import('gnome') subdir('src') subdir('po') subdir('examples') subdir('tests') subdir('doc') subdir('glade') run_data = configuration_data() run_data.set('ABS_BUILDDIR', meson.current_build_dir()) run_data.set('ABS_SRCDIR', meson.current_source_dir()) configure_file( input: 'run.in', output: 'run', configuration: run_data) summary = [ '', '------', 'Handy @0@ (@1@)'.format(current, apiversion), '', ' Tests: @0@'.format(get_option('tests')), ' Examples: @0@'.format(get_option('examples')), ' Documentation: @0@'.format(get_option('gtk_doc')), ' Static: @0@'.format(static), ' Introspection: @0@'.format(introspection), ' Vapi: @0@'.format(get_option('vapi')), ' Glade Catalog: @0@'.format(glade_catalog), '------', '' ] message('\n'.join(summary)) libhandy-0.0.13/meson_options.txt000066400000000000000000000016171360136463700170400ustar00rootroot00000000000000# Performance and debugging related options option('profiling', type: 'boolean', value: false) option('static', type: 'boolean', value: false, description: 'Build as a static library' ) option('introspection', type: 'feature', value: 'auto') option('vapi', type: 'boolean', value: true) # Subproject option('package_subdir', type: 'string', description: 'Subdirectory to append to all installed files, for use as subproject' ) option('gtk_doc', type: 'boolean', value: false, description: 'Whether to generate the API reference for Handy') option('tests', type: 'boolean', value: true, description: 'Whether to compile unit tests') option('examples', type: 'boolean', value: true, description: 'Build and install the examples and demo applications') option('glade_catalog', type: 'feature', value: 'auto', description: 'Install a glade catalog file') libhandy-0.0.13/po/000077500000000000000000000000001360136463700140145ustar00rootroot00000000000000libhandy-0.0.13/po/LINGUAS000066400000000000000000000000001360136463700150270ustar00rootroot00000000000000libhandy-0.0.13/po/POTFILES.in000066400000000000000000000003441360136463700155720ustar00rootroot00000000000000src/gtkprogresstracker.c src/hdy-arrows.c src/hdy-column.c src/hdy-dialer-button.c src/hdy-dialer.c src/hdy-dialer-cycle-button.c src/hdy-fold.c src/hdy-header-group.c src/hdy-leaflet.c src/hdy-string-utf8.c src/hdy-title-bar.c libhandy-0.0.13/po/libhandy.pot000066400000000000000000000122231360136463700163320ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the libhandy package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: libhandy\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-10-15 11:34+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: src/hdy-arrows.c:418 msgid "Number of arrows" msgstr "" #: src/hdy-arrows.c:419 msgid "Number of arrows to display" msgstr "" #: src/hdy-arrows.c:427 msgid "Arrows Direction" msgstr "" #: src/hdy-arrows.c:428 msgid "Direction the arrows should point to" msgstr "" #: src/hdy-arrows.c:435 msgid "Arrow animation duration" msgstr "" #: src/hdy-arrows.c:436 msgid "The duration of the arrow animation in milliseconds" msgstr "" #: src/hdy-column.c:249 msgid "Maximum width" msgstr "" #: src/hdy-column.c:250 msgid "The maximum width allocated to the child" msgstr "" #: src/hdy-column.c:261 msgid "Linear growth width" msgstr "" #: src/hdy-column.c:262 msgid "The width up to which the child will be allocated all the width" msgstr "" #: src/hdy-dialer-button.c:196 msgid "Digit" msgstr "" #: src/hdy-dialer-button.c:197 msgid "The dialer digit of the button" msgstr "" #: src/hdy-dialer-button.c:203 msgid "Symbols" msgstr "" #: src/hdy-dialer-button.c:204 msgid "The dialer symbols of the button" msgstr "" #: src/hdy-dialer.c:405 msgid "Number" msgstr "" #: src/hdy-dialer.c:406 msgid "The phone number to dial" msgstr "" #: src/hdy-dialer.c:412 msgid "Show action buttons" msgstr "" #: src/hdy-dialer.c:413 msgid "Whether to show the submit and delete buttons" msgstr "" #: src/hdy-dialer.c:419 msgid "Column spacing" msgstr "" #: src/hdy-dialer.c:420 msgid "The amount of space between two consecutive columns" msgstr "" #: src/hdy-dialer.c:426 msgid "Row spacing" msgstr "" #: src/hdy-dialer.c:427 msgid "The amount of space between two consecutive rows" msgstr "" #: src/hdy-dialer.c:438 msgid "Main buttons' border relief" msgstr "" #: src/hdy-dialer.c:439 msgid "The border relief style of the main buttons" msgstr "" #: src/hdy-dialer-cycle-button.c:161 msgid "Cycle timeout" msgstr "" #: src/hdy-dialer-cycle-button.c:162 msgid "The timeout (in seconds) between button presses afterwhich a cycle ends" msgstr "" #: src/hdy-header-group.c:570 msgid "Focus" msgstr "" #: src/hdy-header-group.c:571 msgid "The header bar that should have the focus" msgstr "" #: src/hdy-leaflet.c:2711 msgid "Fold" msgstr "" #: src/hdy-leaflet.c:2712 src/hdy-leaflet.c:2729 msgid "Whether the widget is folded" msgstr "" #: src/hdy-leaflet.c:2728 msgid "Folded" msgstr "" #: src/hdy-leaflet.c:2740 msgid "Horizontally homogeneous folded" msgstr "" #: src/hdy-leaflet.c:2741 msgid "Horizontally homogeneous sizing when the leaflet is folded" msgstr "" #: src/hdy-leaflet.c:2752 msgid "Vertically homogeneous folded" msgstr "" #: src/hdy-leaflet.c:2753 msgid "Vertically homogeneous sizing when the leaflet is folded" msgstr "" #: src/hdy-leaflet.c:2764 msgid "Box horizontally homogeneous" msgstr "" #: src/hdy-leaflet.c:2765 msgid "Horizontally homogeneous sizing when the leaflet is unfolded" msgstr "" #: src/hdy-leaflet.c:2776 msgid "Box vertically homogeneous" msgstr "" #: src/hdy-leaflet.c:2777 msgid "Vertically homogeneous sizing when the leaflet is unfolded" msgstr "" #: src/hdy-leaflet.c:2783 msgid "Visible child" msgstr "" #: src/hdy-leaflet.c:2784 msgid "The widget currently visible when the leaflet is folded" msgstr "" #: src/hdy-leaflet.c:2790 msgid "Name of visible child" msgstr "" #: src/hdy-leaflet.c:2791 msgid "The name of the widget currently visible when the children are stacked" msgstr "" #: src/hdy-leaflet.c:2797 msgid "Mode transition type" msgstr "" #: src/hdy-leaflet.c:2798 msgid "The type of animation used to transition between modes" msgstr "" #: src/hdy-leaflet.c:2804 msgid "Mode transition duration" msgstr "" #: src/hdy-leaflet.c:2805 msgid "The mode transition animation duration, in milliseconds" msgstr "" #: src/hdy-leaflet.c:2811 msgid "Child transition type" msgstr "" #: src/hdy-leaflet.c:2812 msgid "The type of animation used to transition between children" msgstr "" #: src/hdy-leaflet.c:2818 msgid "Child transition duration" msgstr "" #: src/hdy-leaflet.c:2819 msgid "The child transition animation duration, in milliseconds" msgstr "" #: src/hdy-leaflet.c:2825 msgid "Child transition running" msgstr "" #: src/hdy-leaflet.c:2826 msgid "Whether or not the child transition is currently running" msgstr "" #: src/hdy-leaflet.c:2832 msgid "Interpolate size" msgstr "" #: src/hdy-leaflet.c:2833 msgid "" "Whether or not the size should smoothly change when changing between " "differently sized children" msgstr "" #: src/hdy-leaflet.c:2841 msgid "Name" msgstr "" #: src/hdy-leaflet.c:2842 msgid "The name of the child page" msgstr "" #: src/hdy-title-bar.c:184 msgid "Selection mode" msgstr "" #: src/hdy-title-bar.c:185 msgid "Whether or not the title bar is in selection mode" msgstr "" libhandy-0.0.13/po/meson.build000066400000000000000000000001611360136463700161540ustar00rootroot00000000000000i18n = import('i18n') i18n.gettext('libhandy', preset : 'glib', install : installable) libhandy-0.0.13/run.in000077500000000000000000000007101360136463700145330ustar00rootroot00000000000000#!/bin/sh ABS_BUILDDIR='@ABS_BUILDDIR@' ABS_SRCDIR='@ABS_SRCDIR@' export GLADE_CATALOG_SEARCH_PATH="${ABS_SRCDIR}/glade/:${GLADE_CATALOG_SEARCH_PATH}" export GLADE_MODULE_SEARCH_PATH="${ABS_BUILDDIR}/glade:${GLADE_MODULE_SEARCH_PATH}" export GI_TYPELIB_PATH="${ABS_BUILDDIR}/src:$GI_TYPELIB_PATH" export LD_LIBRARY_PATH="${ABS_BUILDDIR}/src:${ABS_BUILDDIR}/glade:$LD_LIBRARY_PATH" export PKG_CONFIG_PATH="${ABS_BUILDDIR}/src:$PKG_CONFIG_PATH" exec "$@" libhandy-0.0.13/src/000077500000000000000000000000001360136463700141655ustar00rootroot00000000000000libhandy-0.0.13/src/gen-public-types.sh000066400000000000000000000005221360136463700177070ustar00rootroot00000000000000#!/bin/sh set -e echo '/* This file was generated by gen-plublic-types.sh, do not edit it. */ ' for var in "$@" do echo "#include \"$var\"" done echo '#include "hdy-main-private.h" void hdy_init_public_types (void) {' sed -ne 's/^#define \{1,\}\(HDY_TYPE_[A-Z0-9_]\{1,\}\) \{1,\}.*/ g_type_ensure (\1);/p' "$@" | sort echo '} ' libhandy-0.0.13/src/gtk-window-private.h000066400000000000000000000005131360136463700200770ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS void hdy_gtk_window_toggle_maximized (GtkWindow *window); GdkPixbuf *hdy_gtk_window_get_icon_for_size (GtkWindow *window, gint size); G_END_DECLS libhandy-0.0.13/src/gtk-window.c000066400000000000000000000101661360136463700164270ustar00rootroot00000000000000/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * SPDX-License-Identifier: LGPL-2.1+ */ /* * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ /* Bits taken from GTK 3.24 and tweaked to be used by libhandy. */ #include "gtk-window-private.h" typedef struct { GList *icon_list; gchar *icon_name; guint realized : 1; guint using_default_icon : 1; guint using_parent_icon : 1; guint using_themed_icon : 1; } GtkWindowIconInfo; static GQuark quark_gtk_window_icon_info = 0; static void ensure_quarks (void) { if (!quark_gtk_window_icon_info) quark_gtk_window_icon_info = g_quark_from_static_string ("gtk-window-icon-info"); } void hdy_gtk_window_toggle_maximized (GtkWindow *window) { if (gtk_window_is_maximized (window)) gtk_window_unmaximize (window); else gtk_window_maximize (window); } static GtkWindowIconInfo* get_icon_info (GtkWindow *window) { ensure_quarks (); return g_object_get_qdata (G_OBJECT (window), quark_gtk_window_icon_info); } static void free_icon_info (GtkWindowIconInfo *info) { g_free (info->icon_name); g_slice_free (GtkWindowIconInfo, info); } static GtkWindowIconInfo* ensure_icon_info (GtkWindow *window) { GtkWindowIconInfo *info; ensure_quarks (); info = get_icon_info (window); if (info == NULL) { info = g_slice_new0 (GtkWindowIconInfo); g_object_set_qdata_full (G_OBJECT (window), quark_gtk_window_icon_info, info, (GDestroyNotify)free_icon_info); } return info; } static GdkPixbuf * icon_from_list (GList *list, gint size) { GdkPixbuf *best; GdkPixbuf *pixbuf; GList *l; best = NULL; for (l = list; l; l = l->next) { pixbuf = list->data; if (gdk_pixbuf_get_width (pixbuf) <= size && gdk_pixbuf_get_height (pixbuf) <= size) { best = g_object_ref (pixbuf); break; } } if (best == NULL) best = gdk_pixbuf_scale_simple (GDK_PIXBUF (list->data), size, size, GDK_INTERP_BILINEAR); return best; } static GdkPixbuf * icon_from_name (const gchar *name, gint size) { return gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), name, size, GTK_ICON_LOOKUP_FORCE_SIZE, NULL); } GdkPixbuf * hdy_gtk_window_get_icon_for_size (GtkWindow *window, gint size) { GtkWindowIconInfo *info; const gchar *name; g_autoptr (GList) default_icon_list = gtk_window_get_default_icon_list (); info = ensure_icon_info (window); if (info->icon_list != NULL) return icon_from_list (info->icon_list, size); name = gtk_window_get_icon_name (window); if (name != NULL) return icon_from_name (name, size); if (gtk_window_get_transient_for (window) != NULL) { info = ensure_icon_info (gtk_window_get_transient_for (window)); if (info->icon_list) return icon_from_list (info->icon_list, size); } if (default_icon_list != NULL) return icon_from_list (default_icon_list, size); if (gtk_window_get_default_icon_name () != NULL) return icon_from_name (gtk_window_get_default_icon_name (), size); return NULL; } libhandy-0.0.13/src/gtkprogresstracker.c000066400000000000000000000167471360136463700202760ustar00rootroot00000000000000/* * Copyright © 2016 Endless Mobile Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: Matthew Watson */ #include "gtkprogresstrackerprivate.h" /* #include "gtkprivate.h" */ /* #include "gtkcsseasevalueprivate.h" */ #include #include #include "hdy-animation-private.h" /* * Progress tracker is small helper for tracking progress through gtk * animations. It's a simple zero-initable struct, meant to be thrown in a * widget's private data without the need for setup or teardown. * * Progress tracker will handle translating frame clock timestamps to a * fractional progress value for interpolating between animation targets. * * Progress tracker will use the GTK_SLOWDOWN environment variable to control * the speed of animations. This can be useful for debugging. */ static gdouble gtk_slowdown = 1.0; /** * gtk_progress_tracker_init_copy: * @source: The source progress tracker * @dest: The destination progress tracker * * Copy all progress tracker state from the source tracker to dest tracker. **/ void gtk_progress_tracker_init_copy (GtkProgressTracker *source, GtkProgressTracker *dest) { memcpy (dest, source, sizeof (GtkProgressTracker)); } /** * gtk_progress_tracker_start: * @tracker: The progress tracker * @duration: Animation duration in us * @delay: Animation delay in us * @iteration_count: Number of iterations to run the animation, must be >= 0 * * Begins tracking progress for a new animation. Clears all previous state. **/ void gtk_progress_tracker_start (GtkProgressTracker *tracker, guint64 duration, gint64 delay, gdouble iteration_count) { tracker->is_running = TRUE; tracker->last_frame_time = 0; tracker->duration = duration; tracker->iteration = - delay / (gdouble) duration; tracker->iteration_count = iteration_count; } /** * gtk_progress_tracker_finish: * @tracker: The progress tracker * * Stops running the current animation. **/ void gtk_progress_tracker_finish (GtkProgressTracker *tracker) { tracker->is_running = FALSE; } /** * gtk_progress_tracker_advance_frame: * @tracker: The progress tracker * @frame_time: The current frame time, usually from the frame clock. * * Increments the progress of the animation forward a frame. If no animation has * been started, does nothing. **/ void gtk_progress_tracker_advance_frame (GtkProgressTracker *tracker, guint64 frame_time) { gdouble delta; if (!tracker->is_running) return; if (tracker->last_frame_time == 0) { tracker->last_frame_time = frame_time; return; } if (frame_time < tracker->last_frame_time) { g_warning ("Progress tracker frame set backwards, ignoring."); return; } delta = (frame_time - tracker->last_frame_time) / gtk_slowdown / tracker->duration; tracker->last_frame_time = frame_time; tracker->iteration += delta; } /** * gtk_progress_tracker_skip_frame: * @tracker: The progress tracker * @frame_time: The current frame time, usually from the frame clock. * * Does not update the progress of the animation forward, but records the frame * to calculate future deltas. Calling this each frame will effectively pause * the animation. **/ void gtk_progress_tracker_skip_frame (GtkProgressTracker *tracker, guint64 frame_time) { if (!tracker->is_running) return; tracker->last_frame_time = frame_time; } /** * gtk_progress_tracker_get_state: * @tracker: The progress tracker * * Returns whether the tracker is before, during or after the currently started * animation. The tracker will only ever be in the before state if the animation * was started with a delay. If no animation has been started, returns * %GTK_PROGRESS_STATE_AFTER. * * Returns: A GtkProgressState **/ GtkProgressState gtk_progress_tracker_get_state (GtkProgressTracker *tracker) { if (!tracker->is_running || tracker->iteration > tracker->iteration_count) return GTK_PROGRESS_STATE_AFTER; if (tracker->iteration < 0) return GTK_PROGRESS_STATE_BEFORE; return GTK_PROGRESS_STATE_DURING; } /** * gtk_progress_tracker_get_iteration: * @tracker: The progress tracker * * Returns the fractional number of cycles the animation has completed. For * example, it you started an animation with iteration-count of 2 and are half * way through the second animation, this returns 1.5. * * Returns: The current iteration. **/ gdouble gtk_progress_tracker_get_iteration (GtkProgressTracker *tracker) { return tracker->is_running ? CLAMP (tracker->iteration, 0.0, tracker->iteration_count) : 1.0; } /** * gtk_progress_tracker_get_iteration_cycle: * @tracker: The progress tracker * * Returns an integer index of the current iteration cycle tracker is * progressing through. Handles edge cases, such as an iteration value of 2.0 * which could be considered the end of the second iteration of the beginning of * the third, in the same way as gtk_progress_tracker_get_progress(). * * Returns: The integer count of the current animation cycle. **/ guint64 gtk_progress_tracker_get_iteration_cycle (GtkProgressTracker *tracker) { gdouble iteration = gtk_progress_tracker_get_iteration (tracker); /* Some complexity here. We want an iteration of 0.0 to always map to 0 (start * of the first iteration), but an iteration of 1.0 to also map to 0 (end of * first iteration) and 2.0 to 1 (end of the second iteration). */ if (iteration == 0.0) return 0; return (guint64) ceil (iteration) - 1; } /** * gtk_progress_tracker_get_progress: * @tracker: The progress tracker * @reversed: If progress should be reversed. * * Gets the progress through the current animation iteration, from [0, 1]. Use * to interpolate between animation targets. If reverse is true each iteration * will begin at 1 and end at 0. * * Returns: The progress value. **/ gdouble gtk_progress_tracker_get_progress (GtkProgressTracker *tracker, gboolean reversed) { gdouble progress, iteration; guint64 iteration_cycle; iteration = gtk_progress_tracker_get_iteration (tracker); iteration_cycle = gtk_progress_tracker_get_iteration_cycle (tracker); progress = iteration - iteration_cycle; return reversed ? 1.0 - progress : progress; } /** * gtk_progress_tracker_get_ease_out_cubic: * @tracker: The progress tracker * @reversed: If progress should be reversed before applying the ease function. * * Applies a simple ease out cubic function to the result of * gtk_progress_tracker_get_progress(). * * Returns: The eased progress value. **/ gdouble gtk_progress_tracker_get_ease_out_cubic (GtkProgressTracker *tracker, gboolean reversed) { gdouble progress = gtk_progress_tracker_get_progress (tracker, reversed); return hdy_ease_out_cubic (progress); } libhandy-0.0.13/src/gtkprogresstrackerprivate.h000066400000000000000000000055161360136463700216660ustar00rootroot00000000000000/* * Copyright © 2016 Endless Mobile Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: Matthew Watson */ #ifndef __GTK_PROGRESS_TRACKER_PRIVATE_H__ #define __GTK_PROGRESS_TRACKER_PRIVATE_H__ #include G_BEGIN_DECLS typedef enum { GTK_PROGRESS_STATE_BEFORE, GTK_PROGRESS_STATE_DURING, GTK_PROGRESS_STATE_AFTER, } GtkProgressState; typedef struct _GtkProgressTracker GtkProgressTracker; struct _GtkProgressTracker { gboolean is_running; guint64 last_frame_time; guint64 duration; gdouble iteration; gdouble iteration_count; }; void gtk_progress_tracker_init_copy (GtkProgressTracker *source, GtkProgressTracker *dest); void gtk_progress_tracker_start (GtkProgressTracker *tracker, guint64 duration, gint64 delay, gdouble iteration_count); void gtk_progress_tracker_finish (GtkProgressTracker *tracker); void gtk_progress_tracker_advance_frame (GtkProgressTracker *tracker, guint64 frame_time); void gtk_progress_tracker_skip_frame (GtkProgressTracker *tracker, guint64 frame_time); GtkProgressState gtk_progress_tracker_get_state (GtkProgressTracker *tracker); gdouble gtk_progress_tracker_get_iteration (GtkProgressTracker *tracker); guint64 gtk_progress_tracker_get_iteration_cycle (GtkProgressTracker *tracker); gdouble gtk_progress_tracker_get_progress (GtkProgressTracker *tracker, gboolean reverse); gdouble gtk_progress_tracker_get_ease_out_cubic (GtkProgressTracker *tracker, gboolean reverse); G_END_DECLS #endif /* __GTK_PROGRESS_TRACKER_PRIVATE_H__ */ libhandy-0.0.13/src/handy.gresources.xml000066400000000000000000000027451360136463700202020ustar00rootroot00000000000000 hdy-action-row.ui hdy-combo-row.ui hdy-dialer.ui hdy-dialer-button.ui hdy-expander-row.ui hdy-keypad.ui hdy-keypad-button.ui hdy-paginator.ui hdy-preferences-group.ui hdy-preferences-page.ui hdy-preferences-window.ui hdy-search-bar.ui hdy-view-switcher-bar.ui hdy-view-switcher-button.ui hdy-combo-row-list.css hdy-leaflet.css hdy-expander-row-arrow.css hdy-keypad-digit.css hdy-keypad-letters.css hdy-keypad-symbol.css hdy-text.css hdy-view-switcher.css hdy-view-switcher-bar-box.css hdy-view-switcher-button.css libhandy-0.0.13/src/handy.h000066400000000000000000000031131360136463700154370ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #if !GTK_CHECK_VERSION(3, 22, 0) # error "libhandy requires gtk+-3.0 >= 3.22.0" #endif #if !GLIB_CHECK_VERSION(2, 50, 0) # error "libhandy requires glib-2.0 >= 2.50.0" #endif #define _HANDY_INSIDE #ifndef HANDY_USE_UNSTABLE_API #error libhandy is unstable API. You must define HANDY_USE_UNSTABLE_API before including handy.h #endif #include "hdy-version.h" #include "hdy-action-row.h" #include "hdy-animation.h" G_GNUC_BEGIN_IGNORE_DEPRECATIONS #include "hdy-arrows.h" G_GNUC_END_IGNORE_DEPRECATIONS #include "hdy-column.h" #include "hdy-combo-row.h" #include "hdy-deprecation-macros.h" G_GNUC_BEGIN_IGNORE_DEPRECATIONS #include "hdy-dialer-button.h" #include "hdy-dialer-cycle-button.h" #include "hdy-dialer.h" G_GNUC_END_IGNORE_DEPRECATIONS #include "hdy-dialog.h" #include "hdy-enum-value-object.h" #include "hdy-expander-row.h" #include "hdy-fold.h" #include "hdy-header-bar.h" #include "hdy-header-group.h" #include "hdy-keypad.h" #include "hdy-leaflet.h" #include "hdy-list-box.h" #include "hdy-main.h" #include "hdy-paginator.h" #include "hdy-preferences-group.h" #include "hdy-preferences-page.h" #include "hdy-preferences-row.h" #include "hdy-preferences-window.h" #include "hdy-search-bar.h" #include "hdy-squeezer.h" #include "hdy-string-utf8.h" #include "hdy-swipe-group.h" #include "hdy-swipeable.h" #include "hdy-title-bar.h" #include "hdy-value-object.h" #include "hdy-view-switcher.h" #include "hdy-view-switcher-bar.h" #undef _HANDY_INSIDE G_END_DECLS libhandy-0.0.13/src/hdy-action-row.c000066400000000000000000000530521360136463700172020ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "hdy-action-row.h" #include /** * SECTION:hdy-action-row * @short_description: A #GtkListBox row used to present actions. * @Title: HdyActionRow * * The #HdyActionRow widget can have a title, a subtitle and an icon. The row * can receive action widgets at its end, prefix widgets at its start or widgets * below it. * * Note that action widgets are packed starting from the end. * * It is convenient to present a list of preferences and their related actions. * * # HdyActionRow as GtkBuildable * * The GtkWindow implementation of the GtkBuildable interface supports setting a * child as an action widget by specifying “action” as the “type” attribute of a * <child> element. * * It also supports setting a child as a prefix widget by specifying “prefix” as * the “type” attribute of a <child> element. * * Since: 0.0.6 */ typedef struct { GtkBox *box; GtkBox *header; GtkImage *image; GtkBox *prefixes; GtkLabel *subtitle; GtkLabel *title; GtkBox *title_box; GtkWidget *previous_parent; gboolean use_underline; GtkWidget *activatable_widget; } HdyActionRowPrivate; static void hdy_action_row_buildable_init (GtkBuildableIface *iface); G_DEFINE_TYPE_WITH_CODE (HdyActionRow, hdy_action_row, HDY_TYPE_PREFERENCES_ROW, G_ADD_PRIVATE (HdyActionRow) G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, hdy_action_row_buildable_init)) static GtkBuildableIface *parent_buildable_iface; enum { PROP_0, PROP_ICON_NAME, PROP_ACTIVATABLE_WIDGET, PROP_SUBTITLE, PROP_TITLE, PROP_USE_UNDERLINE, LAST_PROP, }; static GParamSpec *props[LAST_PROP]; static void row_activated_cb (HdyActionRow *self, GtkListBoxRow *row) { /* No need to use GTK_LIST_BOX_ROW() for a pointer comparison. */ if ((GtkListBoxRow *) self == row) hdy_action_row_activate (self); } static void parent_cb (HdyActionRow *self) { HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self); GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (self)); if (priv->previous_parent != NULL) { g_signal_handlers_disconnect_by_func (priv->previous_parent, G_CALLBACK (row_activated_cb), self); priv->previous_parent = NULL; } if (parent == NULL || !GTK_IS_LIST_BOX (parent)) return; priv->previous_parent = parent; g_signal_connect_swapped (parent, "row-activated", G_CALLBACK (row_activated_cb), self); } static void update_subtitle_visibility (HdyActionRow *self) { HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self); gtk_widget_set_visible (GTK_WIDGET (priv->subtitle), gtk_label_get_text (priv->subtitle) != NULL && g_strcmp0 (gtk_label_get_text (priv->subtitle), "") != 0); } static void hdy_action_row_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyActionRow *self = HDY_ACTION_ROW (object); switch (prop_id) { case PROP_ICON_NAME: g_value_set_string (value, hdy_action_row_get_icon_name (self)); break; case PROP_ACTIVATABLE_WIDGET: g_value_set_object (value, (GObject *) hdy_action_row_get_activatable_widget (self)); break; case PROP_SUBTITLE: g_value_set_string (value, hdy_action_row_get_subtitle (self)); break; case PROP_TITLE: g_value_set_string (value, hdy_action_row_get_title (self)); break; case PROP_USE_UNDERLINE: g_value_set_boolean (value, hdy_action_row_get_use_underline (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_action_row_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyActionRow *self = HDY_ACTION_ROW (object); switch (prop_id) { case PROP_ICON_NAME: hdy_action_row_set_icon_name (self, g_value_get_string (value)); break; case PROP_ACTIVATABLE_WIDGET: hdy_action_row_set_activatable_widget (self, (GtkWidget*) g_value_get_object (value)); break; case PROP_SUBTITLE: hdy_action_row_set_subtitle (self, g_value_get_string (value)); break; case PROP_TITLE: hdy_action_row_set_title (self, g_value_get_string (value)); break; case PROP_USE_UNDERLINE: hdy_action_row_set_use_underline (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_action_row_dispose (GObject *object) { HdyActionRow *self = HDY_ACTION_ROW (object); HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self); if (priv->previous_parent != NULL) { g_signal_handlers_disconnect_by_func (priv->previous_parent, G_CALLBACK (row_activated_cb), self); priv->previous_parent = NULL; } G_OBJECT_CLASS (hdy_action_row_parent_class)->dispose (object); } static void hdy_action_row_show_all (GtkWidget *widget) { HdyActionRow *self = HDY_ACTION_ROW (widget); HdyActionRowPrivate *priv; g_return_if_fail (HDY_IS_ACTION_ROW (self)); priv = hdy_action_row_get_instance_private (self); gtk_container_foreach (GTK_CONTAINER (priv->prefixes), (GtkCallback) gtk_widget_show_all, NULL); GTK_WIDGET_CLASS (hdy_action_row_parent_class)->show_all (widget); } static void hdy_action_row_destroy (GtkWidget *widget) { HdyActionRow *self = HDY_ACTION_ROW (widget); HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self); if (priv->box) { gtk_widget_destroy (GTK_WIDGET (priv->box)); priv->box = NULL; } hdy_action_row_set_activatable_widget (self, NULL); priv->prefixes = NULL; priv->header = NULL; GTK_WIDGET_CLASS (hdy_action_row_parent_class)->destroy (widget); } static void hdy_action_row_add (GtkContainer *container, GtkWidget *child) { HdyActionRow *self = HDY_ACTION_ROW (container); HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self); /* When constructing the widget, we want the box to be added as the child of * the GtkListBoxRow, as an implementation detail. */ if (priv->box == NULL) GTK_CONTAINER_CLASS (hdy_action_row_parent_class)->add (container, child); else gtk_container_add (GTK_CONTAINER (priv->box), child); } typedef struct { HdyActionRow *row; GtkCallback callback; gpointer callback_data; } ForallData; static void for_non_internal_child (GtkWidget *widget, gpointer callback_data) { ForallData *data = callback_data; HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (data->row); if (widget != (GtkWidget *) priv->box && widget != (GtkWidget *) priv->image && widget != (GtkWidget *) priv->prefixes && widget != (GtkWidget *) priv->title_box) data->callback (widget, data->callback_data); } static void hdy_action_row_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { HdyActionRow *self = HDY_ACTION_ROW (container); HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self); ForallData data; if (include_internals) { GTK_CONTAINER_CLASS (hdy_action_row_parent_class)->forall (GTK_CONTAINER (self), include_internals, callback, callback_data); return; } data.row = self; data.callback = callback; data.callback_data = callback_data; if (priv->prefixes) GTK_CONTAINER_GET_CLASS (priv->prefixes)->forall (GTK_CONTAINER (priv->prefixes), include_internals, for_non_internal_child, &data); if (priv->header) GTK_CONTAINER_GET_CLASS (priv->header)->forall (GTK_CONTAINER (priv->header), include_internals, for_non_internal_child, &data); if (priv->box) GTK_CONTAINER_GET_CLASS (priv->box)->forall (GTK_CONTAINER (priv->box), include_internals, for_non_internal_child, &data); } static void hdy_action_row_activate_real (HdyActionRow *self) { HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self); if (priv->activatable_widget) gtk_widget_mnemonic_activate (priv->activatable_widget, FALSE); } static void hdy_action_row_class_init (HdyActionRowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->get_property = hdy_action_row_get_property; object_class->set_property = hdy_action_row_set_property; object_class->dispose = hdy_action_row_dispose; widget_class->destroy = hdy_action_row_destroy; widget_class->show_all = hdy_action_row_show_all; container_class->add = hdy_action_row_add; container_class->forall = hdy_action_row_forall; klass->activate = hdy_action_row_activate_real; /** * HdyActionRow:icon-name: * * The icon name for this row. * * Since: 0.0.6 */ props[PROP_ICON_NAME] = g_param_spec_string ("icon-name", _("Icon name"), _("Icon name"), "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); /** * HdyActionRow:activatable-widget: * * The activatable widget for this row. * * Since: 0.0.7 */ props[PROP_ACTIVATABLE_WIDGET] = g_param_spec_object ("activatable-widget", _("Activatable widget"), _("The widget to be activated when the row is activated"), GTK_TYPE_WIDGET, G_PARAM_READWRITE); /** * HdyActionRow:subtitle: * * The subtitle for this row. * * Since: 0.0.6 */ props[PROP_SUBTITLE] = g_param_spec_string ("subtitle", _("Subtitle"), _("Subtitle"), "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); /** * HdyActionRow:title: * * The title for this row. * * Since: 0.0.6 */ props[PROP_TITLE] = g_param_spec_string ("title", _("Title"), _("Title"), "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); /** * HdyActionRow:use-underline: * * Whether an embedded underline in the text of the title and subtitle labels * indicates a mnemonic. * * Since: 0.0.6 */ props[PROP_USE_UNDERLINE] = g_param_spec_boolean ("use-underline", _("Use underline"), _("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-action-row.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, box); gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, header); gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, image); gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, prefixes); gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, subtitle); gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, title); gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, title_box); } static void hdy_action_row_init (HdyActionRow *self) { gtk_widget_init_template (GTK_WIDGET (self)); update_subtitle_visibility (self); g_signal_connect (self, "notify::parent", G_CALLBACK (parent_cb), NULL); } static void hdy_action_row_buildable_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *type) { if (type && strcmp (type, "action") == 0) hdy_action_row_add_action (HDY_ACTION_ROW (buildable), GTK_WIDGET (child)); else if (type && strcmp (type, "prefix") == 0) hdy_action_row_add_prefix (HDY_ACTION_ROW (buildable), GTK_WIDGET (child)); else parent_buildable_iface->add_child (buildable, builder, child, type); } static void hdy_action_row_buildable_init (GtkBuildableIface *iface) { parent_buildable_iface = g_type_interface_peek_parent (iface); iface->add_child = hdy_action_row_buildable_add_child; } /** * hdy_action_row_new: * * Creates a new #HdyActionRow. * * Returns: a new #HdyActionRow * * Since: 0.0.6 */ HdyActionRow * hdy_action_row_new (void) { return g_object_new (HDY_TYPE_ACTION_ROW, NULL); } /** * hdy_action_row_get_title: * @self: a #HdyActionRow * * Gets the title for @self. * * Returns: the title for @self. * * Since: 0.0.6 */ const gchar * hdy_action_row_get_title (HdyActionRow *self) { HdyActionRowPrivate *priv; g_return_val_if_fail (HDY_IS_ACTION_ROW (self), NULL); priv = hdy_action_row_get_instance_private (self); return gtk_label_get_text (priv->title); } /** * hdy_action_row_set_title: * @self: a #HdyActionRow * @title: the title * * Sets the title for @self. * * Since: 0.0.6 */ void hdy_action_row_set_title (HdyActionRow *self, const gchar *title) { HdyActionRowPrivate *priv; g_return_if_fail (HDY_IS_ACTION_ROW (self)); priv = hdy_action_row_get_instance_private (self); hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (self), title); if (g_strcmp0 (gtk_label_get_text (priv->title), title) == 0) return; gtk_label_set_text (priv->title, title); gtk_widget_set_visible (GTK_WIDGET (priv->title), title != NULL && g_strcmp0 (title, "") != 0); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]); } /** * hdy_action_row_get_subtitle: * @self: a #HdyActionRow * * Gets the subtitle for @self. * * Returns: the subtitle for @self. * * Since: 0.0.6 */ const gchar * hdy_action_row_get_subtitle (HdyActionRow *self) { HdyActionRowPrivate *priv; g_return_val_if_fail (HDY_IS_ACTION_ROW (self), NULL); priv = hdy_action_row_get_instance_private (self); return gtk_label_get_text (priv->subtitle); } /** * hdy_action_row_set_subtitle: * @self: a #HdyActionRow * @subtitle: the subtitle * * Sets the subtitle for @self. * * Since: 0.0.6 */ void hdy_action_row_set_subtitle (HdyActionRow *self, const gchar *subtitle) { HdyActionRowPrivate *priv; g_return_if_fail (HDY_IS_ACTION_ROW (self)); priv = hdy_action_row_get_instance_private (self); if (g_strcmp0 (gtk_label_get_text (priv->subtitle), subtitle) == 0) return; gtk_label_set_text (priv->subtitle, subtitle); gtk_widget_set_visible (GTK_WIDGET (priv->subtitle), subtitle != NULL && g_strcmp0 (subtitle, "") != 0); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUBTITLE]); } /** * hdy_action_row_get_icon_name: * @self: a #HdyActionRow * * Gets the icon name for @self. * * Returns: the icon name for @self. * * Since: 0.0.6 */ const gchar * hdy_action_row_get_icon_name (HdyActionRow *self) { HdyActionRowPrivate *priv; const gchar *icon_name; g_return_val_if_fail (HDY_IS_ACTION_ROW (self), NULL); priv = hdy_action_row_get_instance_private (self); gtk_image_get_icon_name (priv->image, &icon_name, NULL); return icon_name; } /** * hdy_action_row_set_icon_name: * @self: a #HdyActionRow * @icon_name: the icon name * * Sets the icon name for @self. * * Since: 0.0.6 */ void hdy_action_row_set_icon_name (HdyActionRow *self, const gchar *icon_name) { HdyActionRowPrivate *priv; const gchar *old_icon_name; g_return_if_fail (HDY_IS_ACTION_ROW (self)); priv = hdy_action_row_get_instance_private (self); gtk_image_get_icon_name (priv->image, &old_icon_name, NULL); if (g_strcmp0 (old_icon_name, icon_name) == 0) return; gtk_image_set_from_icon_name (priv->image, icon_name, GTK_ICON_SIZE_INVALID); gtk_widget_set_visible (GTK_WIDGET (priv->image), icon_name != NULL && g_strcmp0 (icon_name, "") != 0); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]); } /** * hdy_action_row_get_activatable_widget: * @self: a #HdyActionRow * * Gets the widget activated when @self is activated. * * Returns: (nullable) (transfer none): the widget activated when @self is * activated, or %NULL if none has been set. * * Since: 0.0.7 */ GtkWidget * hdy_action_row_get_activatable_widget (HdyActionRow *self) { HdyActionRowPrivate *priv; g_return_val_if_fail (HDY_IS_ACTION_ROW (self), NULL); priv = hdy_action_row_get_instance_private (self); return priv->activatable_widget; } static void activatable_widget_weak_notify (gpointer data, GObject *where_the_object_was) { HdyActionRow *self = HDY_ACTION_ROW (data); HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self); priv->activatable_widget = NULL; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTIVATABLE_WIDGET]); } /** * hdy_action_row_set_activatable_widget: * @self: a #HdyActionRow * @widget: (nullable): the target #GtkWidget, or %NULL to unset * * Sets the widget to activate when @self is activated, either by clicking * on it, by calling hdy_action_row_activate(), or via mnemonics in the title or * the subtitle. See the “use_underline” property to enable mnemonics. * * The target widget will be activated by emitting the * GtkWidget::mnemonic-activate signal on it. * * Since: 0.0.7 */ void hdy_action_row_set_activatable_widget (HdyActionRow *self, GtkWidget *widget) { HdyActionRowPrivate *priv; g_return_if_fail (HDY_IS_ACTION_ROW (self)); priv = hdy_action_row_get_instance_private (self); if (priv->activatable_widget == widget) return; if (widget != NULL) g_return_if_fail (GTK_IS_WIDGET (widget)); if (priv->activatable_widget) g_object_weak_unref (G_OBJECT (priv->activatable_widget), activatable_widget_weak_notify, self); priv->activatable_widget = widget; if (priv->activatable_widget != NULL) g_object_weak_ref (G_OBJECT (priv->activatable_widget), activatable_widget_weak_notify, self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTIVATABLE_WIDGET]); } /** * hdy_action_row_get_use_underline: * @self: a #HdyActionRow * * Gets whether an embedded underline in the text of the title and subtitle * labels indicates a mnemonic. See hdy_action_row_set_use_underline(). * * Returns: %TRUE if an embedded underline in the title and subtitle labels * indicates the mnemonic accelerator keys. * * Since: 0.0.6 */ gboolean hdy_action_row_get_use_underline (HdyActionRow *self) { HdyActionRowPrivate *priv; g_return_val_if_fail (HDY_IS_ACTION_ROW (self), FALSE); priv = hdy_action_row_get_instance_private (self); return priv->use_underline; } /** * hdy_action_row_set_use_underline: * @self: a #HdyActionRow * @use_underline: %TRUE if underlines in the text indicate mnemonics * * If true, an underline in the text of the title and subtitle labels indicates * the next character should be used for the mnemonic accelerator key. * * Since: 0.0.6 */ void hdy_action_row_set_use_underline (HdyActionRow *self, gboolean use_underline) { HdyActionRowPrivate *priv; g_return_if_fail (HDY_IS_ACTION_ROW (self)); priv = hdy_action_row_get_instance_private (self); if (priv->use_underline == !!use_underline) return; priv->use_underline = !!use_underline; hdy_preferences_row_set_use_underline (HDY_PREFERENCES_ROW (self), priv->use_underline); gtk_label_set_use_underline (priv->title, priv->use_underline); gtk_label_set_use_underline (priv->subtitle, priv->use_underline); gtk_label_set_mnemonic_widget (priv->title, GTK_WIDGET (self)); gtk_label_set_mnemonic_widget (priv->subtitle, GTK_WIDGET (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_USE_UNDERLINE]); } /** * hdy_action_row_add_action: * @self: a #HdyActionRow * @widget: (allow-none): the action widget * * Adds an action widget to @self. * * Since: 0.0.6 */ void hdy_action_row_add_action (HdyActionRow *self, GtkWidget *widget) { HdyActionRowPrivate *priv; g_return_if_fail (HDY_IS_ACTION_ROW (self)); priv = hdy_action_row_get_instance_private (self); gtk_box_pack_end (priv->header, widget, FALSE, TRUE, 0); } /** * hdy_action_row_add_prefix: * @self: a #HdyActionRow * @widget: (allow-none): the prefix widget * * Adds a prefix widget to @self. * * Since: 0.0.6 */ void hdy_action_row_add_prefix (HdyActionRow *self, GtkWidget *widget) { HdyActionRowPrivate *priv; g_return_if_fail (HDY_IS_ACTION_ROW (self)); priv = hdy_action_row_get_instance_private (self); gtk_box_pack_start (priv->prefixes, widget, FALSE, TRUE, 0); gtk_widget_show (GTK_WIDGET (priv->prefixes)); } void hdy_action_row_activate (HdyActionRow *self) { g_return_if_fail (HDY_IS_ACTION_ROW (self)); HDY_ACTION_ROW_GET_CLASS (self)->activate (self); } libhandy-0.0.13/src/hdy-action-row.h000066400000000000000000000036421360136463700172070ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include "hdy-preferences-row.h" G_BEGIN_DECLS #define HDY_TYPE_ACTION_ROW (hdy_action_row_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyActionRow, hdy_action_row, HDY, ACTION_ROW, HdyPreferencesRow) /** * HdyActionRowClass * @parent_class: The parent class * @activate: Activates the row to trigger its main action. */ struct _HdyActionRowClass { GtkListBoxRowClass parent_class; void (*activate) (HdyActionRow *self); }; HdyActionRow *hdy_action_row_new (void); const gchar *hdy_action_row_get_title (HdyActionRow *self); void hdy_action_row_set_title (HdyActionRow *self, const gchar *title); const gchar *hdy_action_row_get_subtitle (HdyActionRow *self); void hdy_action_row_set_subtitle (HdyActionRow *self, const gchar *subtitle); const gchar *hdy_action_row_get_icon_name (HdyActionRow *self); void hdy_action_row_set_icon_name (HdyActionRow *self, const gchar *icon_name); GtkWidget *hdy_action_row_get_activatable_widget (HdyActionRow *self); void hdy_action_row_set_activatable_widget (HdyActionRow *self, GtkWidget *widget); gboolean hdy_action_row_get_use_underline (HdyActionRow *self); void hdy_action_row_set_use_underline (HdyActionRow *self, gboolean use_underline); void hdy_action_row_add_action (HdyActionRow *self, GtkWidget *widget); void hdy_action_row_add_prefix (HdyActionRow *self, GtkWidget *widget); void hdy_action_row_activate (HdyActionRow *self); G_END_DECLS libhandy-0.0.13/src/hdy-action-row.ui000066400000000000000000000063411360136463700173740ustar00rootroot00000000000000 libhandy-0.0.13/src/hdy-animation-private.h000066400000000000000000000005011360136463700205430ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include "hdy-animation.h" G_BEGIN_DECLS gdouble hdy_lerp (gdouble a, gdouble b, gdouble t); G_END_DECLS libhandy-0.0.13/src/hdy-animation.c000066400000000000000000000032041360136463700170710ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "hdy-animation-private.h" /** * SECTION:hdy-animation * @short_description: Animation helpers * @title: Animation Helpers * * Animation helpers. * * Since: 0.0.11 */ /** * hdy_get_enable_animations: * @widget: a #GtkWidget * * Returns whether animations are enabled for that widget. This should be used * when implementing an animated widget to know whether to animate it or not. * * Returns: %TRUE if animations are enabled for @widget. * * Since: 0.0.11 */ gboolean hdy_get_enable_animations (GtkWidget *widget) { gboolean enable_animations = TRUE; g_assert (GTK_IS_WIDGET (widget)); g_object_get (gtk_widget_get_settings (widget), "gtk-enable-animations", &enable_animations, NULL); return enable_animations; } /** * hdy_lerp: (skip) * @a: the start * @b: the end * @t: the interpolation rate * * Computes the linear interpolation between @a and @b for @t. * * It is currently private because we cargo-culted it and don't understand the * reason for (1.0 - t) instead of t. * * Returns: the linear interpolation between @a and @b for @t. * * Since: 0.0.11 */ gdouble hdy_lerp (gdouble a, gdouble b, gdouble t) { return a + (b - a) * (1.0 - t); } /* From clutter-easing.c, based on Robert Penner's * infamous easing equations, MIT license. */ /** * hdy_ease_out_cubic: * @t: the term * * Computes the ease out for @t. * * Returns: the ease out for @t. * * Since: 0.0.11 */ gdouble hdy_ease_out_cubic (gdouble t) { gdouble p = t - 1; return p * p * p + 1; } libhandy-0.0.13/src/hdy-animation.h000066400000000000000000000005501360136463700170770ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS gboolean hdy_get_enable_animations (GtkWidget *widget); gdouble hdy_ease_out_cubic (gdouble t); G_END_DECLS libhandy-0.0.13/src/hdy-arrows.c000066400000000000000000000411431360136463700164330ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-animation-private.h" #include "hdy-arrows.h" #include "hdy-enums.h" #include "gtkprogresstrackerprivate.h" #define HDY_ARROWS_DEFAULT_THICKNESS 10 /** * SECTION:hdy-arrows * @short_description: Arrows indicating a swipe direction. * @Title: HdyArrows * * The #HdyArrows widget displays arrows indicating a swiping direction. * An animation is run when the widget is mapped or then #hdy_arrows_animate() * is invoked. */ /** * HdyArrowsDirection: * @HDY_ARROWS_DIRECTION_UP: Arrows point upwards * @HDY_ARROWS_DIRECTION_DOWN: Arrows point to the left * @HDY_ARROWS_DIRECTION_LEFT: Arrows point to the right * @HDY_ARROWS_DIRECTION_RIGHT: Arrows point downwards */ typedef struct { guint count; HdyArrowsDirection direction; struct { guint duration; /* animation duration in ms */ guint current; /* current arrow being drawn */ guint tick_id; GtkProgressTracker tracker; } animation; } HdyArrowsPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyArrows, hdy_arrows, GTK_TYPE_DRAWING_AREA) enum { PROP_0, PROP_COUNT, PROP_DIRECTION, PROP_DURATION, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; enum { STYLE_PROP_0, STYLE_PROP_THICKNESS, N_STYLE_PROPS }; static GParamSpec *style_properties [N_STYLE_PROPS]; static void schedule_draw (HdyArrows *self) { gtk_widget_queue_draw (GTK_WIDGET (self)); } static gboolean animation_cb (GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data) { HdyArrows *self = HDY_ARROWS (widget); HdyArrowsPrivate *priv = hdy_arrows_get_instance_private (self); if (!gtk_widget_get_mapped (widget)) gtk_progress_tracker_finish (&priv->animation.tracker); gtk_progress_tracker_advance_frame (&priv->animation.tracker, gdk_frame_clock_get_frame_time (frame_clock)); schedule_draw (self); if (gtk_progress_tracker_get_state (&priv->animation.tracker) == GTK_PROGRESS_STATE_AFTER) { priv->animation.tick_id = 0; return FALSE; } return TRUE; } static void schedule_child_ticks (HdyArrows *self) { HdyArrowsPrivate *priv = hdy_arrows_get_instance_private (self); if (priv->animation.tick_id == 0) { priv->animation.tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), animation_cb, self, NULL); } } static void unschedule_child_ticks (HdyArrows *self) { HdyArrowsPrivate *priv = hdy_arrows_get_instance_private (self); if (priv->animation.tick_id != 0) { gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->animation.tick_id); priv->animation.tick_id = 0; } } static void start_animation (HdyArrows *self) { HdyArrowsPrivate *priv = hdy_arrows_get_instance_private (self); GtkWidget *widget = GTK_WIDGET (self); if (gtk_widget_get_mapped (widget) && hdy_get_enable_animations (widget) && priv->animation.duration > 0.0 && /* Don't schedule an animation when already ongoing. */ priv->animation.tick_id == 0) { gtk_progress_tracker_start (&priv->animation.tracker, priv->animation.duration * 1000, 0, 1.0); schedule_child_ticks (self); } else { unschedule_child_ticks (self); gtk_progress_tracker_finish (&priv->animation.tracker); } schedule_draw (self); } static gboolean map_cb (GtkWidget *widget, gpointer unused) { HdyArrows *self = HDY_ARROWS (widget); HdyArrowsPrivate *priv = hdy_arrows_get_instance_private (self); if (priv->animation.tick_id == 0) start_animation (self); return TRUE; } static void draw_arrow (cairo_t *cr, gdouble x, gdouble y, gdouble width, gdouble height, gdouble thickness, HdyArrowsDirection direction) { gdouble th = thickness / 2.0; switch (direction) { case HDY_ARROWS_DIRECTION_UP: cairo_move_to(cr, x + th, y + height - th); cairo_line_to(cr, x + width / 2.0, y + thickness); cairo_line_to(cr, x + width - th, y + height - th); cairo_stroke (cr); break; case HDY_ARROWS_DIRECTION_DOWN: cairo_move_to(cr, x + th, y + thickness); cairo_line_to(cr, x + width / 2.0, y + height - th); cairo_line_to(cr, x + width - th, y + thickness); cairo_stroke (cr); break; case HDY_ARROWS_DIRECTION_LEFT: cairo_move_to(cr, x + width - th, y + th); cairo_line_to(cr, x + thickness, y + height / 2.0); cairo_line_to(cr, x + width - th, y + height - th); cairo_stroke (cr); break; case HDY_ARROWS_DIRECTION_RIGHT: cairo_move_to(cr, x + thickness, y + th); cairo_line_to(cr, x + width - th, y + height / 2.0); cairo_line_to(cr, x + thickness, y + height - th); cairo_stroke (cr); break; default: g_warning ("Unhandled arrow mode %d", direction); } } static guint get_thickness (HdyArrows *self) { guint thickness; gtk_widget_style_get (GTK_WIDGET (self), "thickness", &thickness, NULL); return thickness; } static gboolean draw_cb (GtkWidget *widget, cairo_t *cr, gpointer data) { HdyArrows *self = HDY_ARROWS (widget); HdyArrowsPrivate *priv = hdy_arrows_get_instance_private (self); guint width, height; GdkRGBA color; GtkStyleContext *context; gdouble ah = 0.0, aw = 0.0; /* arrow width an height */ gdouble xd = 0.0, yd = 0.0; /* offset between arrows */ gdouble x = 0.0, y = 0.0; /* arrow position */ double iter; gint thickness = get_thickness (self); context = gtk_widget_get_style_context (widget); width = gtk_widget_get_allocated_width (widget); height = gtk_widget_get_allocated_height (widget); gtk_render_background (context, cr, 0, 0, width, height); gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color); gdk_cairo_set_source_rgba (cr, &color); if (priv->animation.tick_id) { iter = gtk_progress_tracker_get_iteration (&priv->animation.tracker); iter *= priv->count; } else { /* no animation */ iter = priv->count; } switch (priv->direction) { case HDY_ARROWS_DIRECTION_UP: aw = width; ah = height / priv->count; xd = 0.0; yd = -1.0 * height / priv->count; x = 0.0; y = height - ah; break; case HDY_ARROWS_DIRECTION_DOWN: aw = width; ah = height / priv->count; xd = 0.0; yd = height / priv->count; x = 0.0; y = 0.0; break; case HDY_ARROWS_DIRECTION_LEFT: aw = width / priv->count; ah = height; xd = -1.0 * width / priv->count; yd = 0.0; x = width - aw; y = 0.0; break; case HDY_ARROWS_DIRECTION_RIGHT: aw = width / priv->count; ah = height; xd = 1.0 * width / priv->count; yd = 0.0; x = 0.0; y = 0.0; break; default: g_warning ("Unhandled arrow mode %d", priv->direction); return FALSE; } cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_width (cr, thickness); for (int i = 1; i <= iter; i++) { draw_arrow (cr, x, y, aw, ah, thickness, priv->direction); y += yd; x += xd; } return FALSE; } static void hdy_arrows_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { HdyArrows *self = HDY_ARROWS (object); switch (property_id) { case PROP_COUNT: hdy_arrows_set_count (self, g_value_get_uint (value)); break; case PROP_DIRECTION: hdy_arrows_set_direction (self, g_value_get_enum (value)); break; case PROP_DURATION: hdy_arrows_set_duration (self, g_value_get_uint (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_arrows_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { HdyArrows *self = HDY_ARROWS (object); HdyArrowsPrivate *priv = hdy_arrows_get_instance_private (self); switch (property_id) { case PROP_COUNT: g_value_set_uint (value, priv->count); break; case PROP_DIRECTION: g_value_set_enum (value, priv->direction); break; case PROP_DURATION: g_value_set_enum (value, priv->animation.duration); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /* This private method is prefixed by the call name because it will be a virtual * method in GTK 4. */ static void hdy_arrows_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { HdyArrows *self = HDY_ARROWS (widget); HdyArrowsPrivate *priv = hdy_arrows_get_instance_private (self); guint thickness, facter, size; thickness = get_thickness (self); facter = priv->direction == HDY_ARROWS_DIRECTION_LEFT || priv->direction == HDY_ARROWS_DIRECTION_RIGHT ? 2 : 3; size = thickness * priv->count * facter; if(minimum) *minimum = size; if(natural) *natural = size; } static void get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural) { hdy_arrows_measure (widget, GTK_ORIENTATION_HORIZONTAL, 0, minimum, natural, NULL, NULL); } static void get_preferred_height (GtkWidget *widget, gint *minimum, gint *natural) { hdy_arrows_measure (widget, GTK_ORIENTATION_HORIZONTAL, 0, minimum, natural, NULL, NULL); } static void hdy_arrows_constructed (GObject *object) { HdyArrows *self = HDY_ARROWS (object); g_signal_connect (GTK_WIDGET (self), "draw", G_CALLBACK (draw_cb), NULL); g_signal_connect (GTK_WIDGET (self), "map", G_CALLBACK (map_cb), NULL); } static void hdy_arrows_class_init (HdyArrowsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = hdy_arrows_constructed; object_class->set_property = hdy_arrows_set_property; object_class->get_property = hdy_arrows_get_property; widget_class->get_preferred_width = get_preferred_width; widget_class->get_preferred_height = get_preferred_height; props[PROP_COUNT] = g_param_spec_uint ("count", _("Number of arrows"), _("Number of arrows to display"), 1, G_MAXUINT, 1, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_DIRECTION] = g_param_spec_enum ("direction", _("Arrows Direction"), _("Direction the arrows should point to"), HDY_TYPE_ARROWS_DIRECTION, HDY_ARROWS_DIRECTION_UP, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_DURATION] = g_param_spec_uint ("duration", _("Arrow animation duration"), _("The duration of the arrow animation in milliseconds"), 0, G_MAXUINT, 1000, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); style_properties [STYLE_PROP_THICKNESS] = g_param_spec_uint ("thickness", "Arrows thickness", "Thickness of the arrows", 1, G_MAXUINT, 10, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); gtk_widget_class_install_style_property (widget_class, style_properties [STYLE_PROP_THICKNESS]); gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_ARROW); gtk_widget_class_set_css_name (widget_class, "hdyarrows"); } /** * hdy_arrows_new: * * Create a new #HdyArrows widget. * * Returns: the newly created #HdyArrows widget * * Deprecated: 0.0.12: Use e.g. #GtkImage and CSS animation instead */ GtkWidget *hdy_arrows_new (void) { return g_object_new (HDY_TYPE_ARROWS, NULL); } static void hdy_arrows_init (HdyArrows *self) { HdyArrowsPrivate *priv = hdy_arrows_get_instance_private (self); priv->count = 1; priv->direction = HDY_ARROWS_DIRECTION_UP; priv->animation.duration = 1000; } /** * hdy_arrows_get_count: * @self: a #HdyArrows * * Get the number of arrows displayed in the widget. * * Returns: the current number of arrows * * Deprecated: 0.0.12: Use e.g. #GtkImage and CSS animation instead */ guint hdy_arrows_get_count (HdyArrows *self) { HdyArrowsPrivate *priv; g_return_val_if_fail (HDY_IS_ARROWS (self), 1); priv = hdy_arrows_get_instance_private (self); return priv->count; } /** * hdy_arrows_set_count * @self: a #HdyArrows * @count: the number of arrows to display * * Set the number of arrows to display. * * Deprecated: 0.0.12: Use e.g. #GtkImage and CSS animation instead */ void hdy_arrows_set_count (HdyArrows *self, guint count) { HdyArrowsPrivate *priv; g_return_if_fail (HDY_IS_ARROWS (self)); g_return_if_fail (count >= 1); priv = hdy_arrows_get_instance_private (self); if (priv->count == count) return; priv->count = count; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_COUNT]); hdy_arrows_animate (self); } /** * hdy_arrows_get_direction * @self: a #HdyArrows * * Get the direction the arrows point to * * Returns: the arrows direction * * Deprecated: 0.0.12: Use e.g. #GtkImage and CSS animation instead */ HdyArrowsDirection hdy_arrows_get_direction (HdyArrows *self) { HdyArrowsPrivate *priv; g_return_val_if_fail (HDY_IS_ARROWS (self), FALSE); priv = hdy_arrows_get_instance_private (self); return priv->direction; } /** * hdy_arrows_set_direction * @self: a #HdyArrows * @direction: the arrows direction * * Set the direction the arrows should point to. * * Deprecated: 0.0.12: Use e.g. #GtkImage and CSS animation instead */ void hdy_arrows_set_direction (HdyArrows *self, HdyArrowsDirection direction) { HdyArrowsPrivate *priv; g_return_if_fail (HDY_IS_ARROWS (self)); g_return_if_fail (direction == HDY_ARROWS_DIRECTION_UP || direction == HDY_ARROWS_DIRECTION_DOWN || direction == HDY_ARROWS_DIRECTION_LEFT || direction == HDY_ARROWS_DIRECTION_RIGHT); priv = hdy_arrows_get_instance_private (self); switch (direction) { case HDY_ARROWS_DIRECTION_UP: case HDY_ARROWS_DIRECTION_DOWN: case HDY_ARROWS_DIRECTION_LEFT: case HDY_ARROWS_DIRECTION_RIGHT: break; default: direction = HDY_ARROWS_DIRECTION_UP; } if (priv->direction == direction) return; priv->direction = direction; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DIRECTION]); hdy_arrows_animate (self); } /** * hdy_arrows_get_duration * @self: a #HdyArrows * * Get the duration of the arrows animation. * * Returns: the duration of the animation in ms * * Deprecated: 0.0.12: Use e.g. #GtkImage and CSS animation instead */ HdyArrowsDirection hdy_arrows_get_duration (HdyArrows *self) { HdyArrowsPrivate *priv; g_return_val_if_fail (HDY_IS_ARROWS (self), FALSE); priv = hdy_arrows_get_instance_private (self); return priv->animation.duration; } /** * hdy_arrows_set_duration * @self: a #HdyArrows * @duration: the duration of the animation in ms * * Set the duration of the arrow animation. * * Deprecated: 0.0.12: Use e.g. #GtkImage and CSS animation instead */ void hdy_arrows_set_duration (HdyArrows *self, guint duration) { HdyArrowsPrivate *priv; g_return_if_fail (HDY_IS_ARROWS (self)); priv = hdy_arrows_get_instance_private (self); if (priv->animation.duration == duration) return; priv->animation.duration = duration; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DURATION]); hdy_arrows_animate (self); } /** * hdy_arrows_animate * @self: a #HdyArrows * * Render the arrows animation. * * Deprecated: 0.0.12: Use e.g. #GtkImage and CSS animation instead */ void hdy_arrows_animate (HdyArrows *self) { g_return_if_fail (HDY_IS_ARROWS (self)); if (gtk_widget_get_mapped (GTK_WIDGET (self))) map_cb (GTK_WIDGET (self), NULL); } libhandy-0.0.13/src/hdy-arrows.h000066400000000000000000000031351360136463700164370ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include "hdy-deprecation-macros.h" #include G_BEGIN_DECLS _HDY_DEPRECATED typedef enum { HDY_ARROWS_DIRECTION_UP, HDY_ARROWS_DIRECTION_DOWN, HDY_ARROWS_DIRECTION_LEFT, HDY_ARROWS_DIRECTION_RIGHT, } HdyArrowsDirection; #define HDY_TYPE_ARROWS (hdy_arrows_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyArrows, hdy_arrows, HDY, ARROWS, GtkDrawingArea) /** * HdyArrowsClass: * @parent_class: The parent class */ struct _HdyArrowsClass { GtkDrawingAreaClass parent_class; }; _HDY_DEPRECATED GtkWidget *hdy_arrows_new (void); _HDY_DEPRECATED guint hdy_arrows_get_count (HdyArrows *self); _HDY_DEPRECATED void hdy_arrows_set_count (HdyArrows *self, guint count); _HDY_DEPRECATED void hdy_arrows_set_direction (HdyArrows *self, HdyArrowsDirection direction); _HDY_DEPRECATED HdyArrowsDirection hdy_arrows_get_direction (HdyArrows *self); _HDY_DEPRECATED void hdy_arrows_set_duration (HdyArrows *self, guint duration); _HDY_DEPRECATED guint hdy_arrows_get_duration (HdyArrows *self); _HDY_DEPRECATED void hdy_arrows_animate (HdyArrows *self); G_END_DECLS libhandy-0.0.13/src/hdy-column.c000066400000000000000000000245411360136463700164160ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "hdy-column.h" #include #include #include "hdy-animation-private.h" /** * SECTION:hdy-column * @short_description: A container letting its child grow up to a given width. * @Title: HdyColumn * * The #HdyColumn widget limits the size of the widget it contains to a given * maximum width. The expansion of the child from its minimum to its maximum * size is eased out for a smooth transition. * * If the child requires more than the requested maximum width, it will be * allocated the minimum width it can fit in instead. */ #define HDY_EASE_OUT_TAN_CUBIC 3 enum { PROP_0, PROP_MAXIMUM_WIDTH, PROP_LINEAR_GROWTH_WIDTH, LAST_PROP, }; struct _HdyColumn { GtkBin parent_instance; gint maximum_width; gint linear_growth_width; }; static GParamSpec *props[LAST_PROP]; G_DEFINE_TYPE (HdyColumn, hdy_column, GTK_TYPE_BIN) static void hdy_column_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyColumn *self = HDY_COLUMN (object); switch (prop_id) { case PROP_MAXIMUM_WIDTH: g_value_set_int (value, hdy_column_get_maximum_width (self)); break; case PROP_LINEAR_GROWTH_WIDTH: g_value_set_int (value, hdy_column_get_linear_growth_width (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_column_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyColumn *self = HDY_COLUMN (object); switch (prop_id) { case PROP_MAXIMUM_WIDTH: hdy_column_set_maximum_width (self, g_value_get_int (value)); break; case PROP_LINEAR_GROWTH_WIDTH: hdy_column_set_linear_growth_width (self, g_value_get_int (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static gint get_child_width (HdyColumn *self, gint width) { GtkBin *bin = GTK_BIN (self); GtkWidget *child; gint minimum_width = 0, maximum_width; gdouble amplitude, threshold, progress; child = gtk_bin_get_child (bin); if (child == NULL) return 0; if (gtk_widget_get_visible (child)) gtk_widget_get_preferred_width (child, &minimum_width, NULL); /* Sanitize the minimum width to use for computations. */ minimum_width = MIN (MAX (minimum_width, self->linear_growth_width), self->maximum_width); if (width <= minimum_width) return width; /* Sanitize the maximum width to use for computations. */ maximum_width = MAX (minimum_width, self->maximum_width); amplitude = maximum_width - minimum_width; threshold = (HDY_EASE_OUT_TAN_CUBIC * amplitude + (gdouble) minimum_width); if (width >= threshold) return maximum_width; progress = (width - minimum_width) / (threshold - minimum_width); return hdy_ease_out_cubic (progress) * amplitude + minimum_width; } /* This private method is prefixed by the call name because it will be a virtual * method in GTK 4. */ static void hdy_column_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkBin *bin = GTK_BIN (widget); GtkWidget *child; if (minimum) *minimum = 0; if (natural) *natural = 0; if (minimum_baseline) *minimum_baseline = -1; if (natural_baseline) *natural_baseline = -1; child = gtk_bin_get_child (bin); if (!(child && gtk_widget_get_visible (child))) return; if (orientation == GTK_ORIENTATION_HORIZONTAL) gtk_widget_get_preferred_width (child, minimum, natural); else { gint child_width = get_child_width (HDY_COLUMN (widget), for_size); gtk_widget_get_preferred_height_and_baseline_for_width (child, child_width, minimum, natural, minimum_baseline, natural_baseline); } } static void hdy_column_get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural) { hdy_column_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, minimum, natural, NULL, NULL); } static void hdy_column_get_preferred_height_and_baseline_for_width (GtkWidget *widget, gint width, gint *minimum, gint *natural, gint *minimum_baseline, gint *natural_baseline) { hdy_column_measure (widget, GTK_ORIENTATION_VERTICAL, width, minimum, natural, minimum_baseline, natural_baseline); } static void hdy_column_get_preferred_height (GtkWidget *widget, gint *minimum, gint *natural) { hdy_column_measure (widget, GTK_ORIENTATION_VERTICAL, -1, minimum, natural, NULL, NULL); } static void hdy_column_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { HdyColumn *self = HDY_COLUMN (widget); GtkBin *bin = GTK_BIN (widget); GtkAllocation child_allocation; gint baseline; GtkWidget *child; gtk_widget_set_allocation (widget, allocation); child = gtk_bin_get_child (bin); if (child == NULL) return; child_allocation.width = get_child_width (self, allocation->width); child_allocation.height = allocation->height; if (!gtk_widget_get_has_window (widget)) { /* This allways center the child vertically. */ child_allocation.x = allocation->x + (allocation->width - child_allocation.width) / 2; child_allocation.y = allocation->y; } else { child_allocation.x = 0; child_allocation.y = 0; } baseline = gtk_widget_get_allocated_baseline (widget); gtk_widget_size_allocate_with_baseline (child, &child_allocation, baseline); } static void hdy_column_class_init (HdyColumnClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->get_property = hdy_column_get_property; object_class->set_property = hdy_column_set_property; widget_class->get_preferred_width = hdy_column_get_preferred_width; widget_class->get_preferred_height = hdy_column_get_preferred_height; widget_class->get_preferred_height_and_baseline_for_width = hdy_column_get_preferred_height_and_baseline_for_width; widget_class->size_allocate = hdy_column_size_allocate; gtk_container_class_handle_border_width (container_class); /** * HdyColumn:maximum_width: * * The maximum width to allocate to the child. */ props[PROP_MAXIMUM_WIDTH] = g_param_spec_int ("maximum-width", _("Maximum width"), _("The maximum width allocated to the child"), 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyColumn:linear_growth_width: * * The width up to which the child will be allocated all the width. */ props[PROP_LINEAR_GROWTH_WIDTH] = g_param_spec_int ("linear-growth-width", _("Linear growth width"), _("The width up to which the child will be allocated all the width"), 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_css_name (widget_class, "hdycolumn"); } static void hdy_column_init (HdyColumn *self) { } /** * hdy_column_new: * * Creates a new #HdyColumn. * * Returns: a new #HdyColumn */ HdyColumn * hdy_column_new (void) { return g_object_new (HDY_TYPE_COLUMN, NULL); } /** * hdy_column_get_maximum_width: * @self: a #HdyColumn * * Gets the maximum width to allocate to the contained child. * * Returns: the maximum width to allocate to the contained child. */ gint hdy_column_get_maximum_width (HdyColumn *self) { g_return_val_if_fail (HDY_IS_COLUMN (self), 0); return self->maximum_width; } /** * hdy_column_set_maximum_width: * @self: a #HdyColumn * @maximum_width: the maximum width * * Sets the maximum width to allocate to the contained child. */ void hdy_column_set_maximum_width (HdyColumn *self, gint maximum_width) { g_return_if_fail (HDY_IS_COLUMN (self)); self->maximum_width = maximum_width; } /** * hdy_column_get_linear_growth_width: * @self: a #HdyColumn * * Gets the width up to which the child will be allocated all the available * width and starting from which it will be allocated a portion of the available * width. In bith cases the allocated width won't exceed the declared maximum. * * Returns: the width up to which the child will be allocated all the available * width. */ gint hdy_column_get_linear_growth_width (HdyColumn *self) { g_return_val_if_fail (HDY_IS_COLUMN (self), 0); return self->linear_growth_width; } /** * hdy_column_set_linear_growth_width: * @self: a #HdyColumn * @linear_growth_width: the linear growth width * * Sets the width up to which the child will be allocated all the available * width and starting from which it will be allocated a portion of the available * width. In bith cases the allocated width won't exceed the declared maximum. * */ void hdy_column_set_linear_growth_width (HdyColumn *self, gint linear_growth_width) { g_return_if_fail (HDY_IS_COLUMN (self)); self->linear_growth_width = linear_growth_width; gtk_widget_queue_resize (GTK_WIDGET (self)); } libhandy-0.0.13/src/hdy-column.h000066400000000000000000000014031360136463700164130ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_COLUMN (hdy_column_get_type()) G_DECLARE_FINAL_TYPE (HdyColumn, hdy_column, HDY, COLUMN, GtkBin) HdyColumn *hdy_column_new (void); gint hdy_column_get_maximum_width (HdyColumn *self); void hdy_column_set_maximum_width (HdyColumn *self, gint maximum_width); gint hdy_column_get_linear_growth_width (HdyColumn *self); void hdy_column_set_linear_growth_width (HdyColumn *self, gint linear_growth_width); G_END_DECLS libhandy-0.0.13/src/hdy-combo-row-list.css000066400000000000000000000001571360136463700203410ustar00rootroot00000000000000/* Make the list's background transparent. */ list { border-style: none; background-color: transparent; } libhandy-0.0.13/src/hdy-combo-row.c000066400000000000000000000573371360136463700170360ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "hdy-combo-row.h" #include #include "hdy-list-box.h" #include "hdy-style-private.h" /** * SECTION:hdy-combo-row * @short_description: A #GtkListBox row used to choose from a list of items. * @Title: HdyComboRow * * The #HdyComboRow widget allows the user to choose from a list of valid * choices. The row displays the selected choice. When activated, the row * displays a popover which allows the user to make a new choice. * * The #HdyComboRow uses the model-view pattern; the list of valid choices * is specified in the form of a #GListModel, and the display of the choices can * be adapted to the data in the model via widget creation functions. * * Since: 0.0.6 */ /* * This was mostly inspired by code from the display panel from GNOME Settings. */ typedef struct { HdyComboRowGetNameFunc func; gpointer func_data; GDestroyNotify func_data_destroy; } HdyComboRowGetName; typedef struct { GtkBox *current; GtkImage *image; GtkListBox *list; GtkPopover *popover; gint selected_index; gboolean use_subtitle; HdyComboRowGetName *get_name; GListModel *bound_model; GtkListBoxCreateWidgetFunc create_list_widget_func; GtkListBoxCreateWidgetFunc create_current_widget_func; gpointer create_widget_func_data; /* This is owned by create_widget_func_data, which is ultimately owned by the * list box, and hence should not be destroyed manually. */ HdyComboRowGetName *get_name_internal; } HdyComboRowPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyComboRow, hdy_combo_row, HDY_TYPE_ACTION_ROW) enum { PROP_0, PROP_SELECTED_INDEX, PROP_USE_SUBTITLE, LAST_PROP, }; static GParamSpec *props[LAST_PROP]; static GtkWidget * create_list_label (gpointer item, gpointer user_data) { HdyComboRowGetName *get_name = (HdyComboRowGetName *) user_data; g_autofree gchar *name = get_name->func (item, get_name->func_data); return g_object_new (GTK_TYPE_LABEL, "ellipsize", PANGO_ELLIPSIZE_END, "label", name, "margin", 12, "max-width-chars", 20, "visible", TRUE, "width-chars", 20, "xalign", 0.0, NULL); } static GtkWidget * create_current_label (gpointer item, gpointer user_data) { HdyComboRowGetName *get_name = (HdyComboRowGetName *) user_data; g_autofree gchar *name = NULL; if (get_name->func) name = get_name->func (item, get_name->func_data); return g_object_new (GTK_TYPE_LABEL, "ellipsize", PANGO_ELLIPSIZE_END, "halign", GTK_ALIGN_END, "label", name, "valign", GTK_ALIGN_CENTER, "visible", TRUE, "xalign", 0.0, NULL); } static void get_name_free (HdyComboRowGetName *get_name) { if (get_name == NULL) return; if (get_name->func_data_destroy) get_name->func_data_destroy (get_name->func_data); get_name->func = NULL; get_name->func_data = NULL; get_name->func_data_destroy = NULL; g_free (get_name); } static void update (HdyComboRow *self) { HdyComboRowPrivate *priv = hdy_combo_row_get_instance_private (self); g_autoptr(GObject) item = NULL; g_autofree gchar *name = NULL; GtkWidget *widget; gtk_widget_set_visible (GTK_WIDGET (priv->current), !priv->use_subtitle); gtk_container_foreach (GTK_CONTAINER (priv->current), (GtkCallback) gtk_widget_destroy, NULL); if (priv->bound_model == NULL || g_list_model_get_n_items (priv->bound_model) == 0) { gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); g_assert (priv->selected_index == -1); return; } g_assert (priv->selected_index >= 0 && priv->selected_index <= g_list_model_get_n_items (priv->bound_model)); gtk_widget_set_sensitive (GTK_WIDGET (self), TRUE); item = g_list_model_get_item (priv->bound_model, priv->selected_index); if (priv->use_subtitle) { if (priv->get_name != NULL && priv->get_name->func) name = priv->get_name->func (item, priv->get_name->func_data); else if (priv->get_name_internal != NULL && priv->get_name_internal->func) name = priv->get_name_internal->func (item, priv->get_name_internal->func_data); hdy_action_row_set_subtitle (HDY_ACTION_ROW (self), name); } else { widget = priv->create_current_widget_func (item, priv->create_widget_func_data); gtk_container_add (GTK_CONTAINER (priv->current), widget); } } static void bound_model_changed (GListModel *list, guint index, guint removed, guint added, gpointer user_data) { gint new_idx; HdyComboRow *self = HDY_COMBO_ROW (user_data); HdyComboRowPrivate *priv = hdy_combo_row_get_instance_private (self); /* Selection is in front of insertion/removal point, nothing to do */ if (priv->selected_index > 0 && priv->selected_index < index) return; if (priv->selected_index < index + removed) { /* The item selected item was removed (or none is selected) */ new_idx = -1; } else { /* The item selected item was behind the insertion/removal */ new_idx = priv->selected_index + added - removed; } /* Select the first item if none is selected. */ if (new_idx == -1 && g_list_model_get_n_items (list) > 0) new_idx = 0; hdy_combo_row_set_selected_index (self, new_idx); } static void row_activated_cb (HdyComboRow *self, GtkListBoxRow *row) { hdy_combo_row_set_selected_index (self, gtk_list_box_row_get_index (row)); } static void hdy_combo_row_activate (HdyActionRow *row) { HdyComboRow *self = HDY_COMBO_ROW (row); HdyComboRowPrivate *priv = hdy_combo_row_get_instance_private (self); gtk_popover_popup (priv->popover); } static void destroy_model (HdyComboRow *self) { HdyComboRowPrivate *priv = hdy_combo_row_get_instance_private (self); if (priv->bound_model) { /* Disconnect the bound model *before* releasing it. */ g_signal_handlers_disconnect_by_func (priv->bound_model, bound_model_changed, self); /* Destroy the model and the user data. */ if (priv->list) gtk_list_box_bind_model (priv->list, NULL, NULL, NULL, NULL); priv->bound_model = NULL; priv->create_list_widget_func = NULL; priv->create_current_widget_func = NULL; priv->create_widget_func_data = NULL; } } static void hdy_combo_row_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyComboRow *self = HDY_COMBO_ROW (object); switch (prop_id) { case PROP_SELECTED_INDEX: g_value_set_int (value, hdy_combo_row_get_selected_index (self)); break; case PROP_USE_SUBTITLE: g_value_set_boolean (value, hdy_combo_row_get_use_subtitle (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_combo_row_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyComboRow *self = HDY_COMBO_ROW (object); switch (prop_id) { case PROP_SELECTED_INDEX: hdy_combo_row_set_selected_index (self, g_value_get_int (value)); break; case PROP_USE_SUBTITLE: hdy_combo_row_set_use_subtitle (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_combo_row_dispose (GObject *object) { HdyComboRow *self = HDY_COMBO_ROW (object); HdyComboRowPrivate *priv = hdy_combo_row_get_instance_private (self); destroy_model (self); g_clear_pointer (&priv->get_name, get_name_free); G_OBJECT_CLASS (hdy_combo_row_parent_class)->dispose (object); } typedef struct { HdyComboRow *row; GtkCallback callback; gpointer callback_data; } ForallData; static void for_non_internal_child (GtkWidget *widget, gpointer callback_data) { ForallData *data = callback_data; HdyComboRowPrivate *priv = hdy_combo_row_get_instance_private (data->row); if (widget != (GtkWidget *) priv->current && widget != (GtkWidget *) priv->image) data->callback (widget, data->callback_data); } static void hdy_combo_row_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { HdyComboRow *self = HDY_COMBO_ROW (container); ForallData data; if (include_internals) { GTK_CONTAINER_CLASS (hdy_combo_row_parent_class)->forall (GTK_CONTAINER (self), include_internals, callback, callback_data); return; } data.row = self; data.callback = callback; data.callback_data = callback_data; GTK_CONTAINER_CLASS (hdy_combo_row_parent_class)->forall (GTK_CONTAINER (self), include_internals, for_non_internal_child, &data); } static void hdy_combo_row_class_init (HdyComboRowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); HdyActionRowClass *row_class = HDY_ACTION_ROW_CLASS (klass); object_class->get_property = hdy_combo_row_get_property; object_class->set_property = hdy_combo_row_set_property; object_class->dispose = hdy_combo_row_dispose; container_class->forall = hdy_combo_row_forall; row_class->activate = hdy_combo_row_activate; /** * HdyComboRow:selected-index: * * The index of the selected item in its #GListModel. * * Since: 0.0.7 */ props[PROP_SELECTED_INDEX] = g_param_spec_int ("selected-index", _("Selected index"), _("The index of the selected item"), -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyComboRow:use-subtitle: * * %TRUE to set the current value as the subtitle. * * If you use a custom widget creation function, you will need to give @self * a name conversion closure with hdy_combo_row_set_get_name_func(). * * If %TRUE, you should not access HdyActionRow:subtitle. * * Since: 0.0.10 */ props[PROP_USE_SUBTITLE] = g_param_spec_boolean ("use-subtitle", _("Use subtitle"), _("Set the current value as the subtitle"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-combo-row.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyComboRow, current); gtk_widget_class_bind_template_child_private (widget_class, HdyComboRow, image); gtk_widget_class_bind_template_child_private (widget_class, HdyComboRow, list); gtk_widget_class_bind_template_child_private (widget_class, HdyComboRow, popover); } static void list_init (HdyComboRow *self) { HdyComboRowPrivate *priv = hdy_combo_row_get_instance_private (self); g_autoptr (GtkCssProvider) provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (provider, "/sm/puri/handy/style/hdy-combo-row-list.css"); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->list)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); } static void hdy_combo_row_init (HdyComboRow *self) { HdyComboRowPrivate *priv = hdy_combo_row_get_instance_private (self); gtk_widget_init_template (GTK_WIDGET (self)); list_init (self); priv->selected_index = -1; gtk_list_box_set_header_func (priv->list, hdy_list_box_separator_header, NULL, NULL); g_signal_connect_object (priv->list, "row-activated", G_CALLBACK (gtk_widget_hide), priv->popover, G_CONNECT_SWAPPED); g_signal_connect_object (priv->list, "row-activated", G_CALLBACK (row_activated_cb), self, G_CONNECT_SWAPPED); update (self); } /** * hdy_combo_row_new: * * Creates a new #HdyComboRow. * * Returns: a new #HdyComboRow * * Since: 0.0.6 */ HdyComboRow * hdy_combo_row_new (void) { return g_object_new (HDY_TYPE_COMBO_ROW, NULL); } /** * hdy_combo_row_get_model: * @self: a #HdyComboRow * * Gets the model bound to @self, or %NULL if none is bound. * * Returns: (transfer none) (nullable): the #GListModel bound to @self or %NULL * * Since: 0.0.6 */ GListModel * hdy_combo_row_get_model (HdyComboRow *self) { HdyComboRowPrivate *priv; g_return_val_if_fail (HDY_IS_COMBO_ROW (self), NULL); priv = hdy_combo_row_get_instance_private (self); return priv->bound_model; } /** * hdy_combo_row_bind_model: * @self: a #HdyComboRow * @model: (nullable): the #GListModel to be bound to @self * @create_list_widget_func: (nullable) (scope call): a function that creates * widgets for items to display in the list, or %NULL in case you also passed * %NULL as @model * @create_current_widget_func: (nullable) (scope call): a function that creates * widgets for items to display as the seleted item, or %NULL in case you also * passed %NULL as @model * @user_data: user data passed to @create_list_widget_func and * @create_current_widget_func * @user_data_free_func: function for freeing @user_data * * Binds @model to @self. * * If @self was already bound to a model, that previous binding is destroyed. * * The contents of @self are cleared and then filled with widgets that represent * items from @model. @self is updated whenever @model changes. If @model is * %NULL, @self is left empty. * * Since: 0.0.6 */ void hdy_combo_row_bind_model (HdyComboRow *self, GListModel *model, GtkListBoxCreateWidgetFunc create_list_widget_func, GtkListBoxCreateWidgetFunc create_current_widget_func, gpointer user_data, GDestroyNotify user_data_free_func) { HdyComboRowPrivate *priv; g_return_if_fail (HDY_IS_COMBO_ROW (self)); g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); g_return_if_fail (model == NULL || create_list_widget_func != NULL); g_return_if_fail (model == NULL || create_current_widget_func != NULL); priv = hdy_combo_row_get_instance_private (self); destroy_model (self); gtk_container_foreach (GTK_CONTAINER (priv->current), (GtkCallback) gtk_widget_destroy, NULL); priv->selected_index = -1; if (model == NULL) { update (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_INDEX]); return; } gtk_list_box_bind_model (priv->list, model, create_list_widget_func, user_data, user_data_free_func); /* We don't need take a reference as the list box holds one for us. */ priv->bound_model = model; priv->create_list_widget_func = create_list_widget_func; priv->create_current_widget_func = create_current_widget_func; priv->create_widget_func_data = user_data; g_signal_connect (priv->bound_model, "items-changed", G_CALLBACK (bound_model_changed), self); if (g_list_model_get_n_items (priv->bound_model) > 0) priv->selected_index = 0; update (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_INDEX]); } /** * hdy_combo_row_bind_name_model: * @self: a #HdyComboRow * @model: (nullable): the #GListModel to be bound to @self * @get_name_func: (nullable): a function that creates names for items, or %NULL * in case you also passed %NULL as @model * @user_data: user data passed to @get_name_func * @user_data_free_func: function for freeing @user_data * * Binds @model to @self. * * If @self was already bound to a model, that previous binding is destroyed. * * The contents of @self are cleared and then filled with widgets that represent * items from @model. @self is updated whenever @model changes. If @model is * %NULL, @self is left empty. * * This is more conventient to use than hdy_combo_row_bind_model() if you want * to represent items of the model with names. * * Since: 0.0.6 */ void hdy_combo_row_bind_name_model (HdyComboRow *self, GListModel *model, HdyComboRowGetNameFunc get_name_func, gpointer user_data, GDestroyNotify user_data_free_func) { HdyComboRowPrivate *priv = hdy_combo_row_get_instance_private (self); g_return_if_fail (HDY_IS_COMBO_ROW (self)); g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); g_return_if_fail (model == NULL || get_name_func != NULL); priv->get_name_internal = g_new0 (HdyComboRowGetName, 1); priv->get_name_internal->func = get_name_func; priv->get_name_internal->func_data = user_data; priv->get_name_internal->func_data_destroy = user_data_free_func; hdy_combo_row_bind_model (self, model, create_list_label, create_current_label, priv->get_name_internal, (GDestroyNotify) get_name_free); } /** * hdy_combo_row_set_for_enum: * @self: a #HdyComboRow * @enum_type: the enumeration #GType to be bound to @self * @get_name_func: (nullable): a function that creates names for items, or %NULL * in case you also passed %NULL as @model * @user_data: user data passed to @get_name_func * @user_data_free_func: function for freeing @user_data * * Creates a model for @enum_type and binds it to @self. The items of the model * will be #HdyEnumValueObject objects. * * If @self was already bound to a model, that previous binding is destroyed. * * The contents of @self are cleared and then filled with widgets that represent * items from @model. @self is updated whenever @model changes. If @model is * %NULL, @self is left empty. * * This is more conventient to use than hdy_combo_row_bind_name_model() if you * want to represent values of an enumeration with names. * * See hdy_enum_value_row_name(). * * Since: 0.0.6 */ void hdy_combo_row_set_for_enum (HdyComboRow *self, GType enum_type, HdyComboRowGetEnumValueNameFunc get_name_func, gpointer user_data, GDestroyNotify user_data_free_func) { g_autoptr (GListStore) store = g_list_store_new (HDY_TYPE_ENUM_VALUE_OBJECT); /* g_autoptr for GEnumClass would require glib > 2.56 */ GEnumClass *enum_class = NULL; gsize i; g_return_if_fail (HDY_IS_COMBO_ROW (self)); enum_class = g_type_class_ref (enum_type); for (i = 0; i < enum_class->n_values; i++) { g_autoptr(HdyEnumValueObject) obj = hdy_enum_value_object_new (&enum_class->values[i]); g_list_store_append (store, obj); } hdy_combo_row_bind_name_model (self, G_LIST_MODEL (store), (HdyComboRowGetNameFunc) get_name_func, user_data, user_data_free_func); g_type_class_unref (enum_class); } /** * hdy_combo_row_get_selected_index: * @self: a #GtkListBoxRow * * Gets the index of the selected item in its #GListModel. * * Returns: the index of the selected item, or -1 if no item is selected * * Since: 0.0.7 */ gint hdy_combo_row_get_selected_index (HdyComboRow *self) { HdyComboRowPrivate *priv; g_return_val_if_fail (HDY_IS_COMBO_ROW (self), -1); priv = hdy_combo_row_get_instance_private (self); return priv->selected_index; } /** * hdy_combo_row_set_selected_index: * @self: a #HdyComboRow * @selected_index: the index of the selected item * * Sets the index of the selected item in its #GListModel. * * Since: 0.0.7 */ void hdy_combo_row_set_selected_index (HdyComboRow *self, gint selected_index) { HdyComboRowPrivate *priv; g_return_if_fail (HDY_IS_COMBO_ROW (self)); g_return_if_fail (selected_index >= -1); priv = hdy_combo_row_get_instance_private (self); g_return_if_fail (selected_index >= 0 || priv->bound_model == NULL || g_list_model_get_n_items (priv->bound_model) == 0); g_return_if_fail (selected_index == -1 || (priv->bound_model != NULL && selected_index < g_list_model_get_n_items (priv->bound_model))); if (priv->selected_index == selected_index) return; priv->selected_index = selected_index; update (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_INDEX]); } /** * hdy_combo_row_get_use_subtitle: * @self: a #GtkListBoxRow * * Gets whether the current value of @self should be displayed as its subtitle. * * Returns: whether the current value of @self should be displayed as its subtitle * * Since: 0.0.10 */ gboolean hdy_combo_row_get_use_subtitle (HdyComboRow *self) { HdyComboRowPrivate *priv; g_return_val_if_fail (HDY_IS_COMBO_ROW (self), FALSE); priv = hdy_combo_row_get_instance_private (self); return priv->use_subtitle; } /** * hdy_combo_row_set_use_subtitle: * @self: a #HdyComboRow * @use_subtitle: %TRUE to set the current value as the subtitle * * Sets whether the current value of @self should be displayed as its subtitle. * * If %TRUE, you should not access HdyActionRow:subtitle. * * Since: 0.0.10 */ void hdy_combo_row_set_use_subtitle (HdyComboRow *self, gboolean use_subtitle) { HdyComboRowPrivate *priv; g_return_if_fail (HDY_IS_COMBO_ROW (self)); priv = hdy_combo_row_get_instance_private (self); use_subtitle = !!use_subtitle; if (priv->use_subtitle == use_subtitle) return; priv->use_subtitle = use_subtitle; update (self); if (!use_subtitle) hdy_action_row_set_subtitle (HDY_ACTION_ROW (self), NULL); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_USE_SUBTITLE]); } /** * hdy_combo_row_set_get_name_func: * @self: a #HdyComboRow * @get_name_func: (nullable): a function that creates names for items, or %NULL * in case you also passed %NULL as @model * @user_data: user data passed to @get_name_func * @user_data_free_func: function for freeing @user_data * * Sets a closure to convert items into names. See HdyComboRow:use-subtitle. * * Since: 0.0.10 */ void hdy_combo_row_set_get_name_func (HdyComboRow *self, HdyComboRowGetNameFunc get_name_func, gpointer user_data, GDestroyNotify user_data_free_func) { HdyComboRowPrivate *priv; g_return_if_fail (HDY_IS_COMBO_ROW (self)); priv = hdy_combo_row_get_instance_private (self); get_name_free (priv->get_name); priv->get_name = g_new0 (HdyComboRowGetName, 1); priv->get_name->func = get_name_func; priv->get_name->func_data = user_data; priv->get_name->func_data_destroy = user_data_free_func; } /** * hdy_enum_value_row_name: * @value: the value from the enum from which to get a name * @user_data: (closure): unused user data * * This is a default implementation of #HdyComboRowGetEnumValueNameFunc to be * used with hdy_combo_row_set_for_enum(). If the enumeration has a nickname, it * will return it, otherwise it will return its name. * * Returns: (transfer full): a newly allocated displayable name that represents @value * * Since: 0.0.6 */ gchar * hdy_enum_value_row_name (HdyEnumValueObject *value, gpointer user_data) { g_return_val_if_fail (HDY_IS_ENUM_VALUE_OBJECT (value), NULL); return g_strdup (hdy_enum_value_object_get_nick (value) != NULL ? hdy_enum_value_object_get_nick (value) : hdy_enum_value_object_get_name (value)); } libhandy-0.0.13/src/hdy-combo-row.h000066400000000000000000000073071360136463700170330ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include "hdy-enum-value-object.h" #include "hdy-action-row.h" G_BEGIN_DECLS #define HDY_TYPE_COMBO_ROW (hdy_combo_row_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyComboRow, hdy_combo_row, HDY, COMBO_ROW, HdyActionRow) /** * HdyComboRowGetNameFunc: * @item: (type GObject): the item from the model from which to get a name * @user_data: (closure): user data * * Called for combo rows that are bound to a #GListModel with * hdy_combo_row_bind_name_model() for each item that gets added to the model. * * Returns: (transfer full): a newly allocated displayable name that represents @item */ typedef gchar * (*HdyComboRowGetNameFunc) (gpointer item, gpointer user_data); /** * HdyComboRowGetEnumValueNameFunc: * @value: the value from the enum from which to get a name * @user_data: (closure): user data * * Called for combo rows that are bound to an enumeration with * hdy_combo_row_set_for_enum() for each value from that enumeration. * * Returns: (transfer full): a newly allocated displayable name that represents @value */ typedef gchar * (*HdyComboRowGetEnumValueNameFunc) (HdyEnumValueObject *value, gpointer user_data); /** * HdyComboRowClass * @parent_class: The parent class */ struct _HdyComboRowClass { HdyActionRowClass parent_class; }; HdyComboRow *hdy_combo_row_new (void); GListModel *hdy_combo_row_get_model (HdyComboRow *self); void hdy_combo_row_bind_model (HdyComboRow *self, GListModel *model, GtkListBoxCreateWidgetFunc create_list_widget_func, GtkListBoxCreateWidgetFunc create_current_widget_func, gpointer user_data, GDestroyNotify user_data_free_func); void hdy_combo_row_bind_name_model (HdyComboRow *self, GListModel *model, HdyComboRowGetNameFunc get_name_func, gpointer user_data, GDestroyNotify user_data_free_func); void hdy_combo_row_set_for_enum (HdyComboRow *self, GType enum_type, HdyComboRowGetEnumValueNameFunc get_name_func, gpointer user_data, GDestroyNotify user_data_free_func); gint hdy_combo_row_get_selected_index (HdyComboRow *self); void hdy_combo_row_set_selected_index (HdyComboRow *self, gint selected_index); gboolean hdy_combo_row_get_use_subtitle (HdyComboRow *self); void hdy_combo_row_set_use_subtitle (HdyComboRow *self, gboolean use_subtitle); void hdy_combo_row_set_get_name_func (HdyComboRow *self, HdyComboRowGetNameFunc get_name_func, gpointer user_data, GDestroyNotify user_data_free_func); gchar *hdy_enum_value_row_name (HdyEnumValueObject *value, gpointer user_data); G_END_DECLS libhandy-0.0.13/src/hdy-combo-row.ui000066400000000000000000000030661360136463700172170ustar00rootroot00000000000000 bottom image never 400 True True 12 12 12 12 none True libhandy-0.0.13/src/hdy-deprecation-macros.h000066400000000000000000000020341360136463700206760ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #if defined(HDY_DISABLE_DEPRECATION_WARNINGS) || defined(HANDY_COMPILATION) # define _HDY_DEPRECATED # define _HDY_DEPRECATED_FOR(f) # define _HDY_DEPRECATED_MACRO # define _HDY_DEPRECATED_MACRO_FOR(f) # define _HDY_DEPRECATED_ENUMERATOR # define _HDY_DEPRECATED_ENUMERATOR_FOR(f) # define _HDY_DEPRECATED_TYPE # define _HDY_DEPRECATED_TYPE_FOR(f) #else # define _HDY_DEPRECATED G_DEPRECATED # define _HDY_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) # define _HDY_DEPRECATED_MACRO G_DEPRECATED # define _HDY_DEPRECATED_MACRO_FOR(f) G_DEPRECATED_FOR(f) # define _HDY_DEPRECATED_ENUMERATOR G_DEPRECATED # define _HDY_DEPRECATED_ENUMERATOR_FOR(f) G_DEPRECATED_FOR(f) # define _HDY_DEPRECATED_TYPE G_DEPRECATED # define _HDY_DEPRECATED_TYPE_FOR(f) G_DEPRECATED_FOR(f) #endif libhandy-0.0.13/src/hdy-dialer-button.c000066400000000000000000000220561360136463700176710ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-dialer-button.h" /** * SECTION:hdy-dialer-button * @short_description: A button on a #HdyDialer keypad. * @Title: HdyDialerButton * * The #HdyDialerButton widget is a single button on an #HdyDialer. It * can represent a single symbol (typically a digit) plus an arbitrary * number of symbols that are displayed below it. * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal api */ enum { PROP_0, PROP_DIGIT, PROP_SYMBOLS, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; typedef struct { GtkLabel *label, *secondary_label; gchar *symbols; } HdyDialerButtonPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyDialerButton, hdy_dialer_button, GTK_TYPE_BUTTON) static void format_label(HdyDialerButton *self) { HdyDialerButtonPrivate *priv = hdy_dialer_button_get_instance_private(self); gchar *symbols = priv->symbols != NULL ? priv->symbols : ""; g_autofree gchar *text = NULL; gchar *secondary_text = NULL; if (*symbols != '\0') { secondary_text = g_utf8_find_next_char (symbols, NULL); /* Allocate memory for the first character and '\0'. */ text = g_malloc0 (secondary_text - symbols + 1); g_utf8_strncpy (text, symbols, 1); } else { text = g_malloc0 (sizeof (gchar)); secondary_text = ""; } gtk_label_set_label (priv->label, text); gtk_label_set_label (priv->secondary_label, secondary_text); } static void hdy_dialer_button_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { HdyDialerButton *self = HDY_DIALER_BUTTON (object); HdyDialerButtonPrivate *priv = hdy_dialer_button_get_instance_private(self); switch (property_id) { case PROP_SYMBOLS: g_free (priv->symbols); priv->symbols = g_value_dup_string (value); format_label(self); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_dialer_button_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { HdyDialerButton *self = HDY_DIALER_BUTTON (object); HdyDialerButtonPrivate *priv = hdy_dialer_button_get_instance_private(self); switch (property_id) { case PROP_DIGIT: g_value_set_int (value, hdy_dialer_button_get_digit (self)); break; case PROP_SYMBOLS: g_value_set_string (value, priv->symbols); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /* This private method is prefixed by the call name because it will be a virtual * method in GTK 4. */ static void hdy_dialer_button_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (hdy_dialer_button_parent_class); gint min1, min2, nat1, nat2; if (for_size < 0) { widget_class->get_preferred_width (widget, &min1, &nat1); widget_class->get_preferred_height (widget, &min2, &nat2); } else { if (orientation == GTK_ORIENTATION_HORIZONTAL) widget_class->get_preferred_width_for_height (widget, for_size, &min1, &nat1); else widget_class->get_preferred_height_for_width (widget, for_size, &min1, &nat1); min2 = nat2 = for_size; } if (minimum) *minimum = MAX (min1, min2); if (natural) *natural = MAX (nat1, nat2); } static void hdy_dialer_button_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width) { hdy_dialer_button_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, minimum_width, natural_width, NULL, NULL); } static void hdy_dialer_button_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height) { hdy_dialer_button_measure (widget, GTK_ORIENTATION_VERTICAL, -1, minimum_height, natural_height, NULL, NULL); } static void hdy_dialer_button_get_preferred_width_for_height (GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width) { hdy_dialer_button_measure (widget, GTK_ORIENTATION_HORIZONTAL, height, minimum_width, natural_width, NULL, NULL); } static void hdy_dialer_button_get_preferred_height_for_width (GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height) { hdy_dialer_button_measure (widget, GTK_ORIENTATION_VERTICAL, width, minimum_height, natural_height, NULL, NULL); } static void hdy_dialer_button_finalize (GObject *object) { HdyDialerButton *self = HDY_DIALER_BUTTON (object); HdyDialerButtonPrivate *priv = hdy_dialer_button_get_instance_private(self); g_clear_pointer (&priv->symbols, g_free); G_OBJECT_CLASS (hdy_dialer_button_parent_class)->finalize (object); } static void hdy_dialer_button_class_init (HdyDialerButtonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->set_property = hdy_dialer_button_set_property; object_class->get_property = hdy_dialer_button_get_property; object_class->finalize = hdy_dialer_button_finalize; widget_class->get_preferred_width = hdy_dialer_button_get_preferred_width; widget_class->get_preferred_height = hdy_dialer_button_get_preferred_height; widget_class->get_preferred_width_for_height = hdy_dialer_button_get_preferred_width_for_height; widget_class->get_preferred_height_for_width = hdy_dialer_button_get_preferred_height_for_width; props[PROP_DIGIT] = g_param_spec_int ("digit", _("Digit"), _("The dialer digit of the button"), -1, INT_MAX, 0, G_PARAM_READABLE); props[PROP_SYMBOLS] = g_param_spec_string ("symbols", _("Symbols"), _("The dialer symbols of the button"), "", G_PARAM_READWRITE); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-dialer-button.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyDialerButton, label); gtk_widget_class_bind_template_child_private (widget_class, HdyDialerButton, secondary_label); } /** * hdy_dialer_button_new: * @symbols: (nullable): the symbols displayed on the #HdyDialerButton * * Create a new #HdyDialerButton which displays * @symbols. If * @symbols is %NULL no symbols will be displayed. * * Returns: the newly created #HdyDialerButton widget * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ GtkWidget *hdy_dialer_button_new (const gchar *symbols) { return g_object_new (HDY_TYPE_DIALER_BUTTON, "symbols", symbols, NULL); } static void hdy_dialer_button_init (HdyDialerButton *self) { HdyDialerButtonPrivate *priv = hdy_dialer_button_get_instance_private(self); gtk_widget_init_template (GTK_WIDGET (self)); priv->symbols = NULL; } /** * hdy_dialer_button_get_digit: * @self: a #HdyDialerButton * * Get the #HdyDialerButton's digit. * * Returns: the button's digit * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ gint hdy_dialer_button_get_digit (HdyDialerButton *self) { HdyDialerButtonPrivate *priv; gchar *symbols; g_return_val_if_fail (HDY_IS_DIALER_BUTTON (self), -1); priv = hdy_dialer_button_get_instance_private(self); symbols = priv->symbols; g_return_val_if_fail (symbols != NULL, -1); g_return_val_if_fail (g_ascii_isdigit (*symbols), -1); return *symbols - '0'; } /** * hdy_dialer_button_get_symbols: * @self: a #HdyDialerButton * * Get the #HdyDialerButton's symbols. * * Returns: the button's symbols. * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ const char* hdy_dialer_button_get_symbols (HdyDialerButton *self) { HdyDialerButtonPrivate *priv = hdy_dialer_button_get_instance_private(self); g_return_val_if_fail (HDY_IS_DIALER_BUTTON (self), NULL); return priv->symbols; } libhandy-0.0.13/src/hdy-dialer-button.h000066400000000000000000000015001360136463700176650ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include "hdy-deprecation-macros.h" #include G_BEGIN_DECLS #define HDY_TYPE_DIALER_BUTTON (hdy_dialer_button_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyDialerButton, hdy_dialer_button, HDY, DIALER_BUTTON, GtkButton) _HDY_DEPRECATED struct _HdyDialerButtonClass { GtkButtonClass parent_class; }; _HDY_DEPRECATED GtkWidget *hdy_dialer_button_new (const gchar *symbols); _HDY_DEPRECATED gint hdy_dialer_button_get_digit (HdyDialerButton *self); _HDY_DEPRECATED const char *hdy_dialer_button_get_symbols (HdyDialerButton *self); G_END_DECLS libhandy-0.0.13/src/hdy-dialer-button.ui000066400000000000000000000024141360136463700200600ustar00rootroot00000000000000 libhandy-0.0.13/src/hdy-dialer-cycle-button.c000066400000000000000000000234721360136463700207710ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-dialer-cycle-button.h" /** * SECTION:hdy-dialer-cycle-button * @short_description: A button on a #HdyDialer keypad cycling through available symbols. * @Title: HdyDialerCycleButton * * The #HdyDialerCycleButton widget is a single button on an #HdyDialer * representing symbols such as digits, letters, #, + * or ☃. When the button is pressed multiple times in a row, the * symbols are cycled through. That is a call to #get_curent_symbol * returns another symbol each time the button is pressed. If no * further button presses are received, cycling mode ends after a * timeout. This is configurable via the * #HdyDialerCycleButton:cycle-timeout property. * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal */ typedef struct { int num; /* number of button presses in the cycle */ guint32 source_id; /* timeout handler id */ gint timeout; /* timeout between button presses */ } HdyDialerCycleButtonPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyDialerCycleButton, hdy_dialer_cycle_button, HDY_TYPE_DIALER_BUTTON) enum { PROP_0, PROP_CYCLE_TIMEOUT, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; enum { SIGNAL_CYCLE_START, SIGNAL_CYCLE_END, SIGNAL_LAST_SIGNAL, }; static guint signals [SIGNAL_LAST_SIGNAL]; static void end_cycle (HdyDialerCycleButton *self) { HdyDialerCycleButtonPrivate *priv = hdy_dialer_cycle_button_get_instance_private(HDY_DIALER_CYCLE_BUTTON (self)); priv->num = 0; priv->source_id = 0; g_signal_emit (self, signals[SIGNAL_CYCLE_END], 0); } static gboolean expire_cb (HdyDialerCycleButton *self) { g_return_val_if_fail (HDY_IS_DIALER_CYCLE_BUTTON (self), FALSE); end_cycle (self); return FALSE; } static gboolean button_clicked_cb (HdyDialerCycleButton *self, GdkEventButton *event) { HdyDialerCycleButtonPrivate *priv = hdy_dialer_cycle_button_get_instance_private(HDY_DIALER_CYCLE_BUTTON (self)); g_return_val_if_fail (HDY_IS_DIALER_CYCLE_BUTTON (self), FALSE); /* Only cycle if we have more than one symbol */ if (strlen (hdy_dialer_button_get_symbols (HDY_DIALER_BUTTON (self))) < 2) return FALSE; if (hdy_dialer_cycle_button_is_cycling (self)) { g_source_remove (priv->source_id); priv->num++; } else { g_signal_emit (self, signals[SIGNAL_CYCLE_START], 0); } priv->source_id = g_timeout_add (priv->timeout, (GSourceFunc) expire_cb, self); return FALSE; } static void hdy_dialer_cycle_button_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { HdyDialerCycleButton *self = HDY_DIALER_CYCLE_BUTTON (object); HdyDialerCycleButtonPrivate *priv = hdy_dialer_cycle_button_get_instance_private (self); switch (property_id) { case PROP_CYCLE_TIMEOUT: priv->timeout = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_dialer_cycle_button_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { HdyDialerCycleButton *self = HDY_DIALER_CYCLE_BUTTON (object); HdyDialerCycleButtonPrivate *priv = hdy_dialer_cycle_button_get_instance_private (self); switch (property_id) { case PROP_CYCLE_TIMEOUT: g_value_set_int (value, priv->timeout); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_dialer_cycle_button_dispose (GObject *object) { HdyDialerCycleButton *self = HDY_DIALER_CYCLE_BUTTON (object); HdyDialerCycleButtonPrivate *priv = hdy_dialer_cycle_button_get_instance_private (self); if (priv->source_id) { g_source_remove (priv->source_id); priv->source_id = 0; } G_OBJECT_CLASS (hdy_dialer_cycle_button_parent_class)->dispose (object); } static void hdy_dialer_cycle_button_class_init (HdyDialerCycleButtonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = hdy_dialer_cycle_button_dispose; object_class->set_property = hdy_dialer_cycle_button_set_property; object_class->get_property = hdy_dialer_cycle_button_get_property; props[PROP_CYCLE_TIMEOUT] = g_param_spec_int ("cycle-timeout", _("Cycle timeout"), _("The timeout (in seconds) between button presses after" "which a cycle ends"), 0, G_MAXINT, 1000, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); /** * HdyDialerCycleButton::cycle-start: * @self: The #HdyDialer instance. * * This signal is emitted when the button starts cycling (that is on * the first button press). * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ signals[SIGNAL_CYCLE_START] = g_signal_new ("cycle-start", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (HdyDialerCycleButtonClass, cycle_start), NULL, NULL, NULL, G_TYPE_NONE, 0); /** * HdyDialerCycleButton::cycle-end: * @self: The #HdyDialer instance. * * This signal is emitted when the cycle ends. This can either be * because of timeout or because #hdy_dialer_cycle_stop_cycle got * called. * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ signals[SIGNAL_CYCLE_END] = g_signal_new ("cycle-end", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (HdyDialerCycleButtonClass, cycle_end), NULL, NULL, NULL, G_TYPE_NONE, 0); } /** * hdy_dialer_cycle_button_new: * @symbols: the symbols displayed on the #HdyDialerCycleButton * * Create a new #HdyDialerCycleButton which displays @symbols. The * symbols can by cycled through by pressing the button multiple * times. * * Returns: the newly created #HdyDialerCycleButton widget * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ GtkWidget *hdy_dialer_cycle_button_new (const gchar* symbols) { /* FIXME: we should call this 'symbols' in the base class already */ return g_object_new (HDY_TYPE_DIALER_CYCLE_BUTTON, "symbols", symbols, NULL); } static void hdy_dialer_cycle_button_init (HdyDialerCycleButton *self) { GtkStyleContext *context; GObject *secondary_label; g_signal_connect (self, "clicked", G_CALLBACK (button_clicked_cb), NULL); end_cycle (self); secondary_label = gtk_widget_get_template_child (GTK_WIDGET (self), HDY_TYPE_DIALER_BUTTON, "secondary_label"); context = gtk_widget_get_style_context (GTK_WIDGET (secondary_label)); gtk_style_context_remove_class (context, "dim-label"); } /** * hdy_dialer_cycle_button_get_current_symbol: * @self: a #HdyDialerCycleButton * * Get the symbol the dialer should display * * Returns: a pointer to the symbol * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ gunichar hdy_dialer_cycle_button_get_current_symbol (HdyDialerCycleButton *self) { HdyDialerCycleButtonPrivate *priv = hdy_dialer_cycle_button_get_instance_private (self); const gchar *symbols = hdy_dialer_button_get_symbols (HDY_DIALER_BUTTON (self)); gint off = priv->num % g_utf8_strlen (symbols, -1); return g_utf8_get_char (g_utf8_offset_to_pointer (symbols, off)); } /** * hdy_dialer_cycle_button_is_cycling: * @self: a #HdyDialerCycleButton * * Check whether the button is in cycling mode. * * Returns: #TRUE if the in cycling mode otherwise #FALSE * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ gboolean hdy_dialer_cycle_button_is_cycling (HdyDialerCycleButton *self) { HdyDialerCycleButtonPrivate *priv = hdy_dialer_cycle_button_get_instance_private (self); return !!priv->source_id; } /** * hdy_dialer_cycle_button_stop_cycle: * @self: a #HdyDialerCycleButton * * Stop the cycling mode. * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ void hdy_dialer_cycle_button_stop_cycle (HdyDialerCycleButton *self) { HdyDialerCycleButtonPrivate *priv = hdy_dialer_cycle_button_get_instance_private(self); g_return_if_fail (HDY_IS_DIALER_CYCLE_BUTTON (self)); if (priv->source_id) { g_source_remove (priv->source_id); priv->source_id = 0; } end_cycle(self); } /** * hdy_dialer_cycle_button_get_cycle_timeout: * @self: a #HdyDialerCycleButton * * Get the cycle timeout in milliseconds. * * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ gint hdy_dialer_cycle_button_get_cycle_timeout (HdyDialerCycleButton *self) { HdyDialerCycleButtonPrivate *priv = hdy_dialer_cycle_button_get_instance_private(self); g_return_val_if_fail (HDY_IS_DIALER_CYCLE_BUTTON (self), 0); return priv->timeout; } /** * hdy_dialer_cycle_button_set_cycle_timeout: * @self: a #HdyDialerCycleButton * @timeout: the timeout in milliseconds * * Set the cycle timeout in milliseconds. * Deprecated: 0.0.12: This widget is considered a #HdyDialer internal * api */ void hdy_dialer_cycle_button_set_cycle_timeout (HdyDialerCycleButton *self, gint timeout) { g_return_if_fail (HDY_IS_DIALER_CYCLE_BUTTON (self)); g_object_set (G_OBJECT (self), "cycle-timeout", timeout, NULL); } libhandy-0.0.13/src/hdy-dialer-cycle-button.h000066400000000000000000000032401360136463700207650ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include "hdy-deprecation-macros.h" #include "hdy-dialer-button.h" G_BEGIN_DECLS #define HDY_TYPE_DIALER_CYCLE_BUTTON (hdy_dialer_cycle_button_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyDialerCycleButton, hdy_dialer_cycle_button, HDY, DIALER_CYCLE_BUTTON, HdyDialerButton) /** * HdyDialerCycleButtonClass: * @parent_class: The parent classqn * @cycle_start: Class handler for the #HdyDialerCycleButton::cycle-start signal * @cycle_end: Class handler for the #HdyDialerCycleButton::cycle-end signal */ _HDY_DEPRECATED struct _HdyDialerCycleButtonClass { HdyDialerButtonClass parent_class; /* Signals */ void (*cycle_start) (HdyDialerCycleButton *self); void (*cycle_end) (HdyDialerCycleButton *self); }; _HDY_DEPRECATED GtkWidget *hdy_dialer_cycle_button_new (const gchar *symbols); _HDY_DEPRECATED gunichar hdy_dialer_cycle_button_get_current_symbol (HdyDialerCycleButton *self); _HDY_DEPRECATED gboolean hdy_dialer_cycle_button_is_cycling (HdyDialerCycleButton *self); _HDY_DEPRECATED void hdy_dialer_cycle_button_stop_cycle (HdyDialerCycleButton *self); _HDY_DEPRECATED gint hdy_dialer_cycle_button_get_cycle_timeout (HdyDialerCycleButton *self); _HDY_DEPRECATED void hdy_dialer_cycle_button_set_cycle_timeout (HdyDialerCycleButton *self, gint timeout); G_END_DECLS libhandy-0.0.13/src/hdy-dialer.c000066400000000000000000000507211360136463700163600ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-dialer.h" #include "hdy-dialer-button.h" #include "hdy-dialer-cycle-button.h" #include "hdy-string-utf8.h" /** * SECTION:hdy-dialer * @short_description: A keypad for dialing numbers. * @Title: HdyDialer * * The #HdyDialer widget is a keypad for entering numbers such as phone numbers * or PIN codes. * * Deprecated: 0.0.12: use #HdyKeypad instead */ typedef struct { GtkGrid *grid; HdyDialerButton *number_btns[10]; HdyDialerCycleButton *btn_hash, *btn_star, *cycle_btn; GtkButton *btn_submit, *btn_del; GtkGesture *long_press_del_gesture; GString *number; gboolean show_action_buttons; GtkReliefStyle relief; } HdyDialerPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyDialer, hdy_dialer, GTK_TYPE_BIN) enum { PROP_0, PROP_NUMBER, PROP_SHOW_ACTION_BUTTONS, PROP_COLUMN_SPACING, PROP_ROW_SPACING, PROP_RELIEF, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; enum { SIGNAL_SUBMITTED, SIGNAL_DELETED, SIGNAL_SYMBOL_CLICKED, SIGNAL_LAST_SIGNAL, }; static guint signals [SIGNAL_LAST_SIGNAL]; static void stop_cycle_mode (HdyDialer *self) { HdyDialerPrivate *priv = hdy_dialer_get_instance_private (self); if (priv->cycle_btn) { hdy_dialer_cycle_button_stop_cycle (priv->cycle_btn); priv->cycle_btn = NULL; } } static void digit_button_clicked (HdyDialer *self, HdyDialerButton *btn) { HdyDialerPrivate *priv = hdy_dialer_get_instance_private (self); int d; g_return_if_fail (HDY_IS_DIALER (self)); g_return_if_fail (HDY_IS_DIALER_BUTTON (btn)); stop_cycle_mode (self); d = hdy_dialer_button_get_digit (btn); g_string_append_printf (priv->number, "%d", d); g_signal_emit(self, signals[SIGNAL_SYMBOL_CLICKED], 0, '0'+d); /* Notify about the number update at the very end so the clicked symbol is received before the notify signal */ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NUMBER]); } static void cycle_button_clicked (HdyDialer *self, HdyDialerCycleButton *btn) { HdyDialerPrivate *priv = hdy_dialer_get_instance_private (self); gunichar symbol; g_return_if_fail (HDY_IS_DIALER (self)); g_return_if_fail (HDY_IS_DIALER_BUTTON (btn)); if (priv->cycle_btn != btn) { stop_cycle_mode (self); priv->cycle_btn = btn; } else if (priv->number->len && hdy_dialer_cycle_button_is_cycling (btn)) { hdy_string_utf8_truncate (priv->number, hdy_string_utf8_len (priv->number)-1); } symbol = hdy_dialer_cycle_button_get_current_symbol (btn); g_string_append_unichar (priv->number, symbol); g_signal_emit(self, signals[SIGNAL_SYMBOL_CLICKED], 0, hdy_dialer_button_get_symbols (HDY_DIALER_BUTTON (btn))[0]); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NUMBER]); } static void cycle_start (HdyDialer *self, HdyDialerCycleButton *btn) { /* FIXME: emit signal */ } static void cycle_end (HdyDialer *self, HdyDialerCycleButton *btn) { HdyDialerPrivate *priv = hdy_dialer_get_instance_private (self); /* reset cycle_btn so pressing it again produces a new character */ if (priv->cycle_btn == btn) { priv->cycle_btn = NULL; /* FIXME: emit signal */ } } static void submit_button_clicked (HdyDialer *self, GtkButton *btn) { HdyDialerPrivate *priv = hdy_dialer_get_instance_private (self); g_return_if_fail (HDY_IS_DIALER (self)); g_return_if_fail (GTK_IS_BUTTON (btn)); stop_cycle_mode (self); g_signal_emit (self, signals[SIGNAL_SUBMITTED], 0, priv->number->str); } static void del_button_clicked (HdyDialer *self, GtkButton *btn) { HdyDialerPrivate *priv = hdy_dialer_get_instance_private (self); g_return_if_fail (HDY_IS_DIALER (self)); g_return_if_fail (GTK_IS_BUTTON (btn)); stop_cycle_mode (self); if (!priv->number->len) return; hdy_string_utf8_truncate (priv->number, hdy_string_utf8_len (priv->number)-1); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NUMBER]); g_signal_emit (self, signals[SIGNAL_DELETED], 0); } static void press_btn (GtkButton *btn, gboolean pressed) { if (pressed) { gtk_widget_set_state_flags (GTK_WIDGET (btn), GTK_STATE_FLAG_CHECKED, FALSE); gtk_button_clicked (btn); } else { gtk_widget_unset_state_flags (GTK_WIDGET (btn), GTK_STATE_FLAG_CHECKED); } } static gboolean key_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer data) { HdyDialerPrivate *priv = hdy_dialer_get_instance_private (HDY_DIALER (widget)); gboolean pressed = !!GPOINTER_TO_INT (data); guint keyval; gdk_event_get_keyval (event, &keyval); switch (keyval) { case GDK_KEY_0 ... GDK_KEY_9: press_btn (GTK_BUTTON (priv->number_btns[keyval % GDK_KEY_0]), pressed); break; case GDK_KEY_numbersign: press_btn (GTK_BUTTON (priv->btn_hash), pressed); break; case GDK_KEY_asterisk: press_btn (GTK_BUTTON (priv->btn_star), pressed); break; case GDK_KEY_Return: if (pressed) gtk_button_clicked (GTK_BUTTON (priv->btn_submit)); break; case GDK_KEY_BackSpace: if (pressed) gtk_button_clicked (GTK_BUTTON (priv->btn_del)); break; default: return FALSE; } return TRUE; } static void grab_focus_cb (HdyDialer *dialer, gpointer unused) { HdyDialerPrivate *priv = hdy_dialer_get_instance_private (dialer); gtk_widget_grab_focus (GTK_WIDGET (priv->number_btns[0])); } static void long_press_del_cb (GtkGestureLongPress *gesture, gdouble x, gdouble y, HdyDialer *self) { stop_cycle_mode (self); hdy_dialer_clear_number (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NUMBER]); g_signal_emit (self, signals[SIGNAL_DELETED], 0); } static void hdy_dialer_finalize (GObject *object) { HdyDialerPrivate *priv = hdy_dialer_get_instance_private (HDY_DIALER (object)); g_string_free (priv->number, TRUE); g_object_unref (priv->long_press_del_gesture); G_OBJECT_CLASS (hdy_dialer_parent_class)->finalize (object); } static void hdy_dialer_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { HdyDialer *self = HDY_DIALER (object); HdyDialerPrivate *priv = hdy_dialer_get_instance_private (self); switch (property_id) { case PROP_COLUMN_SPACING: gtk_grid_set_column_spacing (priv->grid, g_value_get_uint (value)); break; case PROP_NUMBER: g_string_assign (priv->number, g_value_get_string (value)); g_object_notify_by_pspec (object, pspec); break; case PROP_ROW_SPACING: gtk_grid_set_row_spacing (priv->grid, g_value_get_uint (value)); break; case PROP_SHOW_ACTION_BUTTONS: hdy_dialer_set_show_action_buttons (self, g_value_get_boolean (value)); break; case PROP_RELIEF: hdy_dialer_set_relief (self, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_dialer_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { HdyDialer *self = HDY_DIALER (object); HdyDialerPrivate *priv = hdy_dialer_get_instance_private (self); switch (property_id) { case PROP_COLUMN_SPACING: g_value_set_uint (value, gtk_grid_get_column_spacing (priv->grid)); break; case PROP_NUMBER: g_value_set_string (value, priv->number->str); break; case PROP_ROW_SPACING: g_value_set_uint (value, gtk_grid_get_row_spacing (priv->grid)); break; case PROP_SHOW_ACTION_BUTTONS: g_value_set_boolean (value, priv->show_action_buttons); break; case PROP_RELIEF: g_value_set_enum (value, hdy_dialer_get_relief (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_dialer_constructed (GObject *object) { HdyDialer *self = HDY_DIALER (object); HdyDialerPrivate *priv = hdy_dialer_get_instance_private (self); GtkWidget *image; for (int i = 0; i < 10; i++) { g_signal_connect_object (priv->number_btns[i], "clicked", G_CALLBACK (digit_button_clicked), self, G_CONNECT_SWAPPED); } priv->long_press_del_gesture = gtk_gesture_long_press_new (GTK_WIDGET (priv->btn_del)); g_signal_connect (priv->long_press_del_gesture, "pressed", G_CALLBACK (long_press_del_cb), self); gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->long_press_del_gesture), GTK_PHASE_BUBBLE); g_object_connect (priv->btn_star, "swapped-signal::clicked", G_CALLBACK (cycle_button_clicked), self, "swapped-signal::cycle-start", G_CALLBACK (cycle_start), self, "swapped-signal::cycle-end", G_CALLBACK (cycle_end), self, NULL); g_object_connect (priv->btn_hash, "swapped-signal::clicked", G_CALLBACK (cycle_button_clicked), self, "swapped-signal::cycle-start", G_CALLBACK (cycle_start), self, "swapped-signal::cycle-end", G_CALLBACK (cycle_end), self, NULL); g_signal_connect_object (priv->btn_submit, "clicked", G_CALLBACK (submit_button_clicked), self, G_CONNECT_SWAPPED); g_signal_connect_object (priv->btn_del, "clicked", G_CALLBACK (del_button_clicked), self, G_CONNECT_SWAPPED); /* In GTK 4 we can just use the icon-name property */ image = gtk_image_new_from_icon_name ("edit-clear-symbolic", GTK_ICON_SIZE_BUTTON); gtk_button_set_image (priv->btn_del, image); image = gtk_image_new_from_icon_name ("call-start-symbolic", GTK_ICON_SIZE_BUTTON * 1.3); gtk_button_set_image (priv->btn_submit, image); /* Keyboard and focus handling */ gtk_widget_set_events (GTK_WIDGET (self), GDK_KEY_PRESS_MASK); g_signal_connect (G_OBJECT (self), "key_press_event", G_CALLBACK (key_press_event_cb), GINT_TO_POINTER(TRUE)); g_signal_connect (G_OBJECT (self), "key_release_event", G_CALLBACK (key_press_event_cb), GINT_TO_POINTER(FALSE)); g_signal_connect (G_OBJECT (self), "grab-focus", G_CALLBACK (grab_focus_cb), NULL); } static void hdy_dialer_class_init (HdyDialerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = hdy_dialer_constructed; object_class->finalize = hdy_dialer_finalize; object_class->set_property = hdy_dialer_set_property; object_class->get_property = hdy_dialer_get_property; props[PROP_NUMBER] = g_param_spec_string ("number", _("Number"), _("The phone number to dial"), "", G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_SHOW_ACTION_BUTTONS] = g_param_spec_boolean ("show-action-buttons", _("Show action buttons"), _("Whether to show the submit and delete buttons"), TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_COLUMN_SPACING] = g_param_spec_uint ("column-spacing", _("Column spacing"), _("The amount of space between two consecutive columns"), 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_ROW_SPACING] = g_param_spec_uint ("row-spacing", _("Row spacing"), _("The amount of space between two consecutive rows"), 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyDialer:relief: * * The relief style of the edges of the main buttons. */ props[PROP_RELIEF] = g_param_spec_enum ("relief", _("Main buttons' border relief"), _("The border relief style of the main buttons"), GTK_TYPE_RELIEF_STYLE, GTK_RELIEF_NORMAL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); /** * HdyDialer::submitted: * @self: The #HdyDialer instance. * @number: The number at the time of activation. * * This signal is emitted when the dialer's 'dial' button is activated. * Connect to this signal to perform to get notified when the user * wants to submit the dialed number. */ signals[SIGNAL_SUBMITTED] = g_signal_new ("submitted", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (HdyDialerClass, submitted), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); /** * HdyDialer::deleted: * @self: The #HdyDialer instance. * * This signal is emitted when the dialer's 'deleted' button is clicked * to delete the last symbol. */ signals[SIGNAL_DELETED] = g_signal_new ("deleted", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); /** * HdyDialer::symbol-clicked: * @self: The #HdyDialer instance. * @button: The main symbol on the button that was clicked * * This signal is emitted when one of the symbol buttons (0-9, # or *) * is clicked. Connect to this signal to find out which button was pressed. * This doesn't take any cycling modes into account. So the button with "*" * and "+" on it will always send "*". Delete and Submit buttons will * not trigger this signal. */ signals[SIGNAL_SYMBOL_CLICKED] = g_signal_new ("symbol-clicked", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_CHAR); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-dialer.ui"); for (int i=0; i < 10; i++) { g_autofree gchar *name = g_strdup_printf("btn_%d", i); g_return_if_fail (name); gtk_widget_class_bind_template_child_full (widget_class, name, FALSE, G_PRIVATE_OFFSET(HdyDialer, number_btns[i])); } gtk_widget_class_bind_template_child_private (widget_class, HdyDialer, grid); gtk_widget_class_bind_template_child_private (widget_class, HdyDialer, btn_hash); gtk_widget_class_bind_template_child_private (widget_class, HdyDialer, btn_star); gtk_widget_class_bind_template_child_private (widget_class, HdyDialer, btn_submit); gtk_widget_class_bind_template_child_private (widget_class, HdyDialer, btn_del); gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_DIAL); gtk_widget_class_set_css_name (widget_class, "hdydialer"); } /** * hdy_dialer_new: * * Create a new #HdyDialer widget. * * Returns: the newly created #HdyDialer widget * * Deprecated: 0.0.12: use #HdyKeypad instead */ GtkWidget *hdy_dialer_new (void) { return g_object_new (HDY_TYPE_DIALER, NULL); } static void hdy_dialer_init (HdyDialer *self) { HdyDialerPrivate *priv = hdy_dialer_get_instance_private (self); gtk_widget_init_template (GTK_WIDGET (self)); g_object_bind_property (self, "relief", priv->number_btns[0], "relief", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); priv->number = g_string_new (NULL); priv->cycle_btn = NULL; priv->show_action_buttons = TRUE; } /** * hdy_dialer_get_number: * @self: a #HdyDialer * * Get the currently displayed number. * * Returns: (transfer none): the current number in the display * * Deprecated: 0.0.12: use #HdyKeypad instead */ const gchar * hdy_dialer_get_number (HdyDialer *self) { HdyDialerPrivate *priv; g_return_val_if_fail (HDY_IS_DIALER (self), NULL); priv = hdy_dialer_get_instance_private (self); return priv->number->str; } /** * hdy_dialer_set_number: * @self: a #HdyDialer * @number: (transfer none): the number to set * * Set the currently displayed number. * * Deprecated: 0.0.12: use #HdyKeypad instead */ void hdy_dialer_set_number (HdyDialer *self, const gchar *number) { HdyDialerPrivate *priv; g_return_if_fail (HDY_IS_DIALER (self)); g_return_if_fail (number != NULL); priv = hdy_dialer_get_instance_private (self); g_string_assign (priv->number, number); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NUMBER]); } /** * hdy_dialer_clear_number: * @self: a #HdyDialer * * Set the current number to the empty string. When the number is already * cleared no action is performed. * * Deprecated: 0.0.12: use #HdyKeypad instead */ void hdy_dialer_clear_number (HdyDialer *self) { HdyDialerPrivate *priv; g_return_if_fail (HDY_IS_DIALER (self)); priv = hdy_dialer_get_instance_private (self); if (g_strcmp0(priv->number->str, "")) { hdy_dialer_set_number (self, ""); } } /** * hdy_dialer_get_show_action_buttons: * @self: a #HdyDialer * * Get whether the submit and delete buttons are to be shown. * * Returns: whether the buttons are to be shown * * Deprecated: 0.0.12: use #HdyKeypad instead */ gboolean hdy_dialer_get_show_action_buttons (HdyDialer *self) { HdyDialerPrivate *priv; g_return_val_if_fail (HDY_IS_DIALER (self), FALSE); priv = hdy_dialer_get_instance_private (self); return priv->show_action_buttons; } /** * hdy_dialer_set_show_action_buttons: * @self: a #HdyDialer * @show: whether to show the buttons * * Set whether to show the submit and delete buttons. * * Deprecated: 0.0.12: use #HdyKeypad instead */ void hdy_dialer_set_show_action_buttons (HdyDialer *self, gboolean show) { HdyDialerPrivate *priv; g_return_if_fail (HDY_IS_DIALER (self)); priv = hdy_dialer_get_instance_private (self); if (priv->show_action_buttons == show) return; priv->show_action_buttons = show; gtk_widget_set_visible (GTK_WIDGET (priv->btn_submit), show); gtk_widget_set_visible (GTK_WIDGET (priv->btn_del), show); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_ACTION_BUTTONS]); } /** * hdy_dialer_set_relief: * @self: The #HdyDialer whose main buttons you want to set relief styles of * @relief: The #GtkReliefStyle as described above * * Sets the relief style of the edges of the main buttons for the given * #HdyDialer widget. * Two styles exist, %GTK_RELIEF_NORMAL and %GTK_RELIEF_NONE. * The default style is, as one can guess, %GTK_RELIEF_NORMAL. * * Deprecated: 0.0.12: use #HdyKeypad instead */ void hdy_dialer_set_relief (HdyDialer *self, GtkReliefStyle relief) { HdyDialerPrivate *priv; g_return_if_fail (HDY_IS_DIALER (self)); priv = hdy_dialer_get_instance_private (self); if (priv->relief == relief) return; priv->relief = relief; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_RELIEF]); } /** * hdy_dialer_get_relief: * @self: The #HdyDialer whose main buttons you want the #GtkReliefStyle from * * Returns the current relief style of the main buttons for the given * #HdyDialer. * * Returns: The current #GtkReliefStyle * * Deprecated: 0.0.12: use #HdyKeypad instead */ GtkReliefStyle hdy_dialer_get_relief (HdyDialer *self) { HdyDialerPrivate *priv; g_return_val_if_fail (HDY_IS_DIALER (self), GTK_RELIEF_NORMAL); priv = hdy_dialer_get_instance_private (self); return priv->relief; } libhandy-0.0.13/src/hdy-dialer.h000066400000000000000000000031571360136463700163660ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include "hdy-deprecation-macros.h" #include G_BEGIN_DECLS #define HDY_TYPE_DIALER (hdy_dialer_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyDialer, hdy_dialer, HDY, DIALER, GtkBin) /** * HdyDialerClass: * @parent_class: The parent class * @submitted: Class handler for the #HdyDialer::submitted signal */ _HDY_DEPRECATED struct _HdyDialerClass { GtkBinClass parent_class; /* Signals */ void (*submitted) (HdyDialer *self, const gchar *number); }; _HDY_DEPRECATED GtkWidget *hdy_dialer_new (void); _HDY_DEPRECATED const gchar *hdy_dialer_get_number (HdyDialer *self); _HDY_DEPRECATED void hdy_dialer_set_number (HdyDialer *self, const char *number); _HDY_DEPRECATED void hdy_dialer_clear_number (HdyDialer *self); _HDY_DEPRECATED gboolean hdy_dialer_get_show_action_buttons (HdyDialer *self); _HDY_DEPRECATED void hdy_dialer_set_show_action_buttons (HdyDialer *self, gboolean show); _HDY_DEPRECATED GtkReliefStyle hdy_dialer_get_relief (HdyDialer *self); _HDY_DEPRECATED void hdy_dialer_set_relief (HdyDialer *self, GtkReliefStyle relief); G_END_DECLS libhandy-0.0.13/src/hdy-dialer.ui000066400000000000000000000222121360136463700165450ustar00rootroot00000000000000 vertical libhandy-0.0.13/src/hdy-dialog.c000066400000000000000000000310711360136463700163540ustar00rootroot00000000000000/* * Copyright © 2018 Zander Brown * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-dialog.h" /** * SECTION:hdy-dialog * @short_description: An adaptive dialog. * @title: HdyDialog * @See_also: #HdyHeaderBar * * A #GtkDialog that adapts to smaller displays. * * Small is defined as: * |[ * is_small = (( width <= 400 && height <= 800) || * (maximized && width <= 800 && height <= 400)); * ]| * * In the smaller view a HdyDialog matches its size to that of its * parent and for ["Presentation Dialogs"](https://developer.gnome.org/hig/stable/dialogs.html) * uses a back button rather than close button to dismiss. * * It's recommended that dialog contents are wrapped in a #GtkScrolledWindow * to ensure they don't overflow the screen. * * #HdyDialog works best when #GtkDialog:use-header-bar is %TRUE (which is * the case when using hdy_dialog_new()). * * Design Information: [GitLab Issue](https://source.puri.sm/Librem5/libhandy/issues/52) * * Ideally when using #HdyDialog you shouldn't need to know you are using * it rather than #GtkDialog however there are some notable differences: * #GtkWindow:modal is %TRUE by default as is #GtkWindow:destroy-with-parent as * the behaviour demonstrated by #HdyDialog would be a bad user experience * when not modal. * * If you want to replace the titlebar by your own, we recommend using * #HdyHeaderBar as it will retain the abiity to present a back button when the * dialog is small. #HdyHeaderBar doesn't have to be its direct child and you * can use any complex contraption you like as the dialog's titlebar. * * Since: 0.0.7 */ /* Point at which we switch to mobile view */ #define SNAP_POINT_A 400 #define SNAP_POINT_B 800 /* GTK < 3.24.2 never actually emits notify::transient-for so we have this to * work around it. * * We can't have get_property() without set_property(), so we have to implement * it even for GTK < 3.24.2. */ #define TRANSIENT_FOR_WORKAROUND !GTK_CHECK_VERSION(3, 24, 2) enum { PROP_0, PROP_NARROW, /* Wrap the property on GtkWindow */ PROP_TRANSIENT_FOR, LAST_PROP = PROP_NARROW + 1, }; typedef struct { GtkWindow *parent; gulong size_handler; gint old_width, old_height; GtkWidget *closebtn; gboolean no_actions : 1; gboolean is_small : 1; } HdyDialogPrivate; static GParamSpec *props[LAST_PROP]; G_DEFINE_TYPE_WITH_CODE (HdyDialog, hdy_dialog, GTK_TYPE_DIALOG, G_ADD_PRIVATE (HdyDialog)) static void update_titlebar (HdyDialog *self, gboolean is_small) { HdyDialogPrivate *priv = hdy_dialog_get_instance_private (self); GtkWidget *titlebar; titlebar = gtk_window_get_titlebar (GTK_WINDOW (self)); /* We don't know what to do with things that aren't headerbars */ if (!GTK_IS_HEADER_BAR (titlebar)) return; /* Dialog already had close hidden (probably action dialog) */ if (!priv->no_actions) { return; } /* When small show our custom button */ if (is_small) { gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (titlebar), FALSE); gtk_widget_show (priv->closebtn); } else { gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (titlebar), TRUE); gtk_widget_hide (priv->closebtn); } } /* Controls the dialog size, called in reposnse to a GtkWidget::size-allocate * on the parent of GtkWidget::realize on the dialog */ static void handle_size (HdyDialog *self, GtkWindow *parent) { HdyDialogPrivate *priv = hdy_dialog_get_instance_private (self); gint width, height; gboolean maximized; gboolean is_small; if (parent == NULL) return; /* Get the size of the parent */ gtk_window_get_size (parent, &width, &height); maximized = gtk_window_is_maximized (parent); /* The "Should we use mobile view™" logic * Basically: When tall & narrow (but possibly desktop) or * when short & long (but only mobile) * Of course we are assuming being short & long & * maximised only happens on mobile */ is_small = (( width <= SNAP_POINT_A && height <= SNAP_POINT_B) || (maximized && width <= SNAP_POINT_B && height <= SNAP_POINT_A)); /* When we are below the snap point */ if (is_small) { /* When no size is cached, cache the current size */ if (!priv->old_width || !priv->old_height) { gtk_window_get_size (GTK_WINDOW (self), &priv->old_width, &priv->old_height); update_titlebar (self, is_small); } /* Resize the dialog to match the parent */ gtk_window_resize (GTK_WINDOW (self), width, height); } else if (priv->old_width || priv->old_height) { /* Restore the cached size */ gtk_window_resize (GTK_WINDOW (self), priv->old_width, priv->old_height); update_titlebar (self, is_small); /* Clear cached size */ priv->old_width = 0; priv->old_height = 0; } if (priv->is_small == is_small) return; priv->is_small = is_small; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NARROW]); } static void hdy_dialog_realize (GtkWidget *widget) { HdyDialog *self = HDY_DIALOG (widget); HdyDialogPrivate *priv = hdy_dialog_get_instance_private (self); GtkWidget *titlebar; titlebar = gtk_window_get_titlebar (GTK_WINDOW (self)); /* If no titlebar was set, add a headerbar */ if (!titlebar) { titlebar = gtk_header_bar_new (); gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (titlebar), TRUE); gtk_header_bar_set_title (GTK_HEADER_BAR (titlebar), gtk_window_get_title (GTK_WINDOW (self))); gtk_widget_show (titlebar); gtk_window_set_titlebar (GTK_WINDOW (self), titlebar); } /* If the titlebar is a headerbar add the back button to it */ if (GTK_IS_HEADER_BAR (titlebar)) { priv->no_actions = gtk_header_bar_get_show_close_button (GTK_HEADER_BAR (titlebar)); if (priv->no_actions) { gtk_header_bar_pack_start (GTK_HEADER_BAR (titlebar), priv->closebtn); } } handle_size (self, gtk_window_get_transient_for (GTK_WINDOW (self))); GTK_WIDGET_CLASS (hdy_dialog_parent_class)->realize (widget); } static void parent_freed_cb (gpointer data, GObject *where_the_object_was) { HdyDialog *self = HDY_DIALOG (data); HdyDialogPrivate *priv = hdy_dialog_get_instance_private (self); priv->parent = NULL; } static void hdy_dialog_finalize (GObject *object) { HdyDialog *self = HDY_DIALOG (object); HdyDialogPrivate *priv = hdy_dialog_get_instance_private (self); /* If we had a parent disconnect from it */ if (priv->parent) { g_signal_handler_disconnect (G_OBJECT (priv->parent), priv->size_handler); g_object_weak_unref (G_OBJECT (priv->parent), parent_freed_cb, self); } G_OBJECT_CLASS (hdy_dialog_parent_class)->finalize (object); } static void hdy_dialog_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyDialog *self = HDY_DIALOG (object); switch (prop_id) { case PROP_NARROW: g_value_set_boolean (value, hdy_dialog_get_narrow (self)); break; #if TRANSIENT_FOR_WORKAROUND case PROP_TRANSIENT_FOR: g_value_set_object (value, gtk_window_get_transient_for (GTK_WINDOW (self))); break; #endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_dialog_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { #if TRANSIENT_FOR_WORKAROUND HdyDialog *self = HDY_DIALOG (object); #endif switch (prop_id) { #if TRANSIENT_FOR_WORKAROUND case PROP_TRANSIENT_FOR: gtk_window_set_transient_for (GTK_WINDOW (self), g_value_get_object (value)); g_object_notify (G_OBJECT (self), "transient-for"); break; #endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_dialog_class_init (HdyDialogClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->get_property = hdy_dialog_get_property; object_class->set_property = hdy_dialog_set_property; object_class->finalize = hdy_dialog_finalize; widget_class->realize = hdy_dialog_realize; #if TRANSIENT_FOR_WORKAROUND g_object_class_override_property (object_class, PROP_TRANSIENT_FOR, "transient-for"); #endif /** * HdyDialog:narrow: * * %TRUE if the dialog is narrow. * * Since: 0.0.11 */ props[PROP_NARROW] = g_param_spec_boolean ("narrow", _("Narrow"), _("Whether the dialog is narrow"), FALSE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); } /* Handle GtkWidget::size-allocate on (HdyDialog) GtkWindow:transient-for */ static void size_cb (GtkWidget *widget, GdkRectangle *allocation, gpointer user_data) { HdyDialog *self = HDY_DIALOG (user_data); handle_size (self, GTK_WINDOW (widget)); } /* Handle (HdyDialog) GObject::notify::transient-for */ static void transient_cb (GObject *object, GParamSpec *pspec, gpointer user_data) { HdyDialog *self = HDY_DIALOG (object); HdyDialogPrivate *priv = hdy_dialog_get_instance_private (self); /* If we are being reparented disconnect from the old one */ if (priv->parent) { g_signal_handler_disconnect (G_OBJECT (priv->parent), priv->size_handler); g_object_weak_unref (G_OBJECT (priv->parent), parent_freed_cb, self); } /* Get the dialogs new parent */ priv->parent = gtk_window_get_transient_for (GTK_WINDOW (self)); /* Check we actually have a parent */ if (priv->parent) { /* Listen for the parent resizing */ priv->size_handler = g_signal_connect (G_OBJECT (priv->parent), "size-allocate", G_CALLBACK (size_cb), self); gtk_widget_queue_allocate (GTK_WIDGET (priv->parent)); g_object_weak_ref (G_OBJECT (priv->parent), parent_freed_cb, self); } } /* Handle GtkButton::clicked on our custom back button */ static void back_clicked_cb (GtkButton *back, gpointer user_data) { HdyDialog *self = HDY_DIALOG (user_data); g_signal_emit_by_name (self, "close", NULL); } static void hdy_dialog_init (HdyDialog *self) { HdyDialogPrivate *priv = hdy_dialog_get_instance_private (self); /* Set the inital values of our private data */ priv->parent = NULL; priv->size_handler = 0; priv->old_width = 0; priv->old_height = 0; priv->no_actions = TRUE; /* Prepare the back button for the mobile view */ priv->closebtn = gtk_button_new_from_icon_name ("go-previous-symbolic", GTK_ICON_SIZE_BUTTON); gtk_widget_hide (priv->closebtn); g_signal_connect (G_OBJECT (priv->closebtn), "clicked", G_CALLBACK (back_clicked_cb), self); /* Listen to changes in our parent */ g_signal_connect (G_OBJECT (self), "notify::transient-for", G_CALLBACK (transient_cb), NULL); /* Change some properties default values */ g_object_set (G_OBJECT (self), "modal", TRUE, "destroy-with-parent", TRUE, NULL); } /** * hdy_dialog_new: * @parent: #GtkWindow this dialog is a child of * * Create a #HdyDialog with #GtkWindow:transient-for set to parent * * C Usage * |[ * GtkWidget *dlg = hdy_dialog_new (GTK_WINDOW (main_window)); * ]| * * Vala Usage * |[ * var dlg = new Hdy.Dialog (main_window); * ]| * * Python Usage * |[ * dlg = Handy.Dialog.new (main_window); * ]| * * Since: 0.0.7 */ GtkWidget * hdy_dialog_new (GtkWindow *parent) { return g_object_new (HDY_TYPE_DIALOG, "use-header-bar", TRUE, "transient-for", parent, NULL); } /** * hdy_dialog_get_narrow: * @self: a #HdyDialog * * Gets whether @self is narrow. * * Returns: %TRUE if @self is narrow, %FALSE otherwise. * * Since: 0.0.11 */ gboolean hdy_dialog_get_narrow (HdyDialog *self) { HdyDialogPrivate *priv; g_return_val_if_fail (HDY_IS_DIALOG (self), FALSE); priv = hdy_dialog_get_instance_private (self); return priv->is_small; } libhandy-0.0.13/src/hdy-dialog.h000066400000000000000000000010641360136463700163600ustar00rootroot00000000000000/* * Copyright © 2018 Zander Brown * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_DIALOG (hdy_dialog_get_type()) struct _HdyDialogClass { GtkDialogClass parent_class; }; G_DECLARE_DERIVABLE_TYPE (HdyDialog, hdy_dialog, HDY, DIALOG, GtkDialog) GtkWidget *hdy_dialog_new (GtkWindow *parent); gboolean hdy_dialog_get_narrow (HdyDialog *self); G_END_DECLS libhandy-0.0.13/src/hdy-enum-value-object.c000066400000000000000000000026141360136463700204400ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "hdy-enum-value-object.h" /** * SECTION:hdy-enum-value-object * @short_description: An object representing a #GEnumValue. * @Title: HdyEnumValueObject * * The #HdyEnumValueObject object represents a #GEnumValue, allowing it to be * used with #GListModel. * * Since: 0.0.6 */ struct _HdyEnumValueObject { GObject parent_instance; GEnumValue enum_value; }; G_DEFINE_TYPE (HdyEnumValueObject, hdy_enum_value_object, G_TYPE_OBJECT) HdyEnumValueObject * hdy_enum_value_object_new (GEnumValue *enum_value) { HdyEnumValueObject *self = g_object_new (HDY_TYPE_ENUM_VALUE_OBJECT, NULL); self->enum_value = *enum_value; return self; } static void hdy_enum_value_object_class_init (HdyEnumValueObjectClass *klass) { } static void hdy_enum_value_object_init (HdyEnumValueObject *self) { } gint hdy_enum_value_object_get_value (HdyEnumValueObject *self) { g_return_val_if_fail (HDY_IS_ENUM_VALUE_OBJECT (self), 0); return self->enum_value.value; } const gchar * hdy_enum_value_object_get_name (HdyEnumValueObject *self) { g_return_val_if_fail (HDY_IS_ENUM_VALUE_OBJECT (self), NULL); return self->enum_value.value_name; } const gchar * hdy_enum_value_object_get_nick (HdyEnumValueObject *self) { g_return_val_if_fail (HDY_IS_ENUM_VALUE_OBJECT (self), NULL); return self->enum_value.value_nick; } libhandy-0.0.13/src/hdy-enum-value-object.h000066400000000000000000000013561360136463700204470ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include G_BEGIN_DECLS #define HDY_TYPE_ENUM_VALUE_OBJECT (hdy_enum_value_object_get_type()) G_DECLARE_FINAL_TYPE (HdyEnumValueObject, hdy_enum_value_object, HDY, ENUM_VALUE_OBJECT, GObject) HdyEnumValueObject *hdy_enum_value_object_new (GEnumValue *enum_value); gint hdy_enum_value_object_get_value (HdyEnumValueObject *self); const gchar *hdy_enum_value_object_get_name (HdyEnumValueObject *self); const gchar *hdy_enum_value_object_get_nick (HdyEnumValueObject *self); G_END_DECLS libhandy-0.0.13/src/hdy-enums.c.in000066400000000000000000000017131360136463700166510ustar00rootroot00000000000000/*** BEGIN file-header ***/ #include "config.h" #include "hdy-arrows.h" #include "hdy-enums.h" #include "hdy-fold.h" #include "hdy-header-bar.h" #include "hdy-leaflet.h" #include "hdy-paginator.h" #include "hdy-squeezer.h" #include "hdy-view-switcher.h" /*** END file-header ***/ /*** BEGIN file-production ***/ /* enumerations from "@filename@" */ /*** END file-production ***/ /*** BEGIN value-header ***/ GType @enum_name@_get_type (void) { static GType etype = 0; if (G_UNLIKELY(etype == 0)) { static const G@Type@Value values[] = { /*** END value-header ***/ /*** BEGIN value-production ***/ { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, /*** END value-production ***/ /*** BEGIN value-tail ***/ { 0, NULL, NULL } }; etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); } return etype; } /*** END value-tail ***/ /*** BEGIN file-tail ***/ /*** END file-tail ***/ libhandy-0.0.13/src/hdy-enums.h.in000066400000000000000000000010411360136463700166500ustar00rootroot00000000000000/*** BEGIN file-header ***/ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS /*** END file-header ***/ /*** BEGIN file-production ***/ /* enumerations from "@basename@" */ /*** END file-production ***/ /*** BEGIN value-header ***/ GType @enum_name@_get_type (void); #define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) /*** END value-header ***/ /*** BEGIN file-tail ***/ G_END_DECLS /*** END file-tail ***/ libhandy-0.0.13/src/hdy-expander-row-arrow.css000066400000000000000000000007541360136463700212320ustar00rootroot00000000000000/* Animate the arrow's rotation */ row button:checked:dir(ltr) image, row button:checked:dir(rtl) image { transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); -gtk-icon-transform: rotate(0turn); } row button:not(checked):dir(ltr) image { transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); -gtk-icon-transform: rotate(-0.25turn); } row button:not(checked):dir(rtl) image { transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); -gtk-icon-transform: rotate(0.25turn); } libhandy-0.0.13/src/hdy-expander-row.c000066400000000000000000000304361360136463700175340ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "hdy-expander-row.h" #include #include "hdy-style-private.h" /** * SECTION:hdy-expander-row * @short_description: A #GtkListBox row used to reveal widgets. * @Title: HdyExpanderRow * * The #HdyExpanderRow allows the user to reveal or hide widgets below it. It * also allows the user to enable the expansion of the row, allowing to disable * all that the row contains. * * Since: 0.0.6 */ typedef struct { GtkBox *box; GtkToggleButton *button; GtkSwitch *enable_switch; GtkImage *image; GtkRevealer *revealer; GtkSeparator *separator; gboolean expanded; gboolean enable_expansion; gboolean show_enable_switch; } HdyExpanderRowPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyExpanderRow, hdy_expander_row, HDY_TYPE_ACTION_ROW) enum { PROP_0, PROP_EXPANDED, PROP_ENABLE_EXPANSION, PROP_SHOW_ENABLE_SWITCH, LAST_PROP, }; static GParamSpec *props[LAST_PROP]; static void arrow_init (HdyExpanderRow *self) { HdyExpanderRowPrivate *priv = hdy_expander_row_get_instance_private (self); g_autoptr (GtkCssProvider) provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (provider, "/sm/puri/handy/style/hdy-expander-row-arrow.css"); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->image)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); } static void hdy_expander_row_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyExpanderRow *self = HDY_EXPANDER_ROW (object); switch (prop_id) { case PROP_EXPANDED: g_value_set_boolean (value, hdy_expander_row_get_expanded (self)); break; case PROP_ENABLE_EXPANSION: g_value_set_boolean (value, hdy_expander_row_get_enable_expansion (self)); break; case PROP_SHOW_ENABLE_SWITCH: g_value_set_boolean (value, hdy_expander_row_get_show_enable_switch (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_expander_row_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyExpanderRow *self = HDY_EXPANDER_ROW (object); switch (prop_id) { case PROP_EXPANDED: hdy_expander_row_set_expanded (self, g_value_get_boolean (value)); break; case PROP_ENABLE_EXPANSION: hdy_expander_row_set_enable_expansion (self, g_value_get_boolean (value)); break; case PROP_SHOW_ENABLE_SWITCH: hdy_expander_row_set_show_enable_switch (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_expander_row_destroy (GtkWidget *widget) { HdyExpanderRow *self = HDY_EXPANDER_ROW (widget); HdyExpanderRowPrivate *priv = hdy_expander_row_get_instance_private (self); priv->box = NULL; GTK_WIDGET_CLASS (hdy_expander_row_parent_class)->destroy (widget); } static void hdy_expander_row_add (GtkContainer *container, GtkWidget *child) { HdyExpanderRow *self = HDY_EXPANDER_ROW (container); HdyExpanderRowPrivate *priv = hdy_expander_row_get_instance_private (self); /* When constructing the widget, we want the revealer to be added as the child * of the HdyExpanderRow, as an implementation detail. */ if (priv->revealer == NULL) GTK_CONTAINER_CLASS (hdy_expander_row_parent_class)->add (container, child); else gtk_container_add (GTK_CONTAINER (priv->box), child); } typedef struct { HdyExpanderRow *row; GtkCallback callback; gpointer callback_data; } ForallData; static void for_non_internal_child (GtkWidget *widget, gpointer callback_data) { ForallData *data = callback_data; HdyExpanderRowPrivate *priv = hdy_expander_row_get_instance_private (data->row); if (widget != (GtkWidget *) priv->button && widget != (GtkWidget *) priv->enable_switch && widget != (GtkWidget *) priv->revealer && widget != (GtkWidget *) priv->separator) data->callback (widget, data->callback_data); } static void hdy_expander_row_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { HdyExpanderRow *self = HDY_EXPANDER_ROW (container); HdyExpanderRowPrivate *priv = hdy_expander_row_get_instance_private (self); ForallData data; if (include_internals) { GTK_CONTAINER_CLASS (hdy_expander_row_parent_class)->forall (GTK_CONTAINER (self), include_internals, callback, callback_data); return; } data.row = self; data.callback = callback; data.callback_data = callback_data; GTK_CONTAINER_CLASS (hdy_expander_row_parent_class)->forall (GTK_CONTAINER (self), include_internals, for_non_internal_child, &data); if (priv->box) GTK_CONTAINER_GET_CLASS (priv->box)->forall (GTK_CONTAINER (priv->box), include_internals, callback, callback_data); } static void hdy_expander_row_activate (HdyActionRow *row) { HdyExpanderRow *self = HDY_EXPANDER_ROW (row); HdyExpanderRowPrivate *priv = hdy_expander_row_get_instance_private (self); hdy_expander_row_set_expanded (self, priv->enable_expansion); HDY_ACTION_ROW_CLASS (hdy_expander_row_parent_class)->activate (row); } static void hdy_expander_row_class_init (HdyExpanderRowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); HdyActionRowClass *row_class = HDY_ACTION_ROW_CLASS (klass); object_class->get_property = hdy_expander_row_get_property; object_class->set_property = hdy_expander_row_set_property; widget_class->destroy = hdy_expander_row_destroy; container_class->add = hdy_expander_row_add; container_class->forall = hdy_expander_row_forall; row_class->activate = hdy_expander_row_activate; /** * HdyExpanderRow:expanded: * * %TRUE if the row is expanded. */ props[PROP_EXPANDED] = g_param_spec_boolean ("expanded", _("Expanded"), _("Whether the row is expanded"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyExpanderRow:enable-expansion: * * %TRUE if the expansion is enabled. */ props[PROP_ENABLE_EXPANSION] = g_param_spec_boolean ("enable-expansion", _("Enable expansion"), _("Whether the expansion is enabled"), TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyExpanderRow:show-enable-switch: * * %TRUE if the switch enabling the expansion is visible. */ props[PROP_SHOW_ENABLE_SWITCH] = g_param_spec_boolean ("show-enable-switch", _("Show enable switch"), _("Whether the switch enabling the expansion is visible"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-expander-row.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyExpanderRow, box); gtk_widget_class_bind_template_child_private (widget_class, HdyExpanderRow, button); gtk_widget_class_bind_template_child_private (widget_class, HdyExpanderRow, image); gtk_widget_class_bind_template_child_private (widget_class, HdyExpanderRow, revealer); gtk_widget_class_bind_template_child_private (widget_class, HdyExpanderRow, separator); gtk_widget_class_bind_template_child_private (widget_class, HdyExpanderRow, enable_switch); } static void hdy_expander_row_init (HdyExpanderRow *self) { HdyExpanderRowPrivate *priv = hdy_expander_row_get_instance_private (self); gtk_widget_init_template (GTK_WIDGET (self)); arrow_init (self); hdy_expander_row_set_enable_expansion (self, TRUE); hdy_expander_row_set_expanded (self, FALSE); g_object_bind_property (self, "show-enable-switch", priv->separator, "visible", G_BINDING_SYNC_CREATE); g_object_bind_property (self, "show-enable-switch", priv->enable_switch, "visible", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); g_object_bind_property (self, "enable-expansion", priv->enable_switch, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); g_object_bind_property (self, "enable-expansion", priv->button, "sensitive", G_BINDING_SYNC_CREATE); g_object_bind_property (self, "enable-expansion", priv->box, "sensitive", G_BINDING_SYNC_CREATE); } /** * hdy_expander_row_new: * * Creates a new #HdyExpanderRow. * * Returns: a new #HdyExpanderRow * * Since: 0.0.6 */ HdyExpanderRow * hdy_expander_row_new (void) { return g_object_new (HDY_TYPE_EXPANDER_ROW, NULL); } gboolean hdy_expander_row_get_expanded (HdyExpanderRow *self) { HdyExpanderRowPrivate *priv; g_return_val_if_fail (HDY_IS_EXPANDER_ROW (self), FALSE); priv = hdy_expander_row_get_instance_private (self); return priv->expanded; } void hdy_expander_row_set_expanded (HdyExpanderRow *self, gboolean expanded) { HdyExpanderRowPrivate *priv; g_return_if_fail (HDY_IS_EXPANDER_ROW (self)); priv = hdy_expander_row_get_instance_private (self); expanded = !!expanded && priv->enable_expansion; if (priv->expanded == expanded) return; priv->expanded = expanded; gtk_revealer_set_reveal_child (priv->revealer, expanded); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_EXPANDED]); } /** * hdy_expander_row_get_enable_expansion: * @self: a #HdyExpanderRow * * Gets whether the expansion of @self is enabled. * * Returns: whether the expansion of @self is enabled. * * Since: 0.0.6 */ gboolean hdy_expander_row_get_enable_expansion (HdyExpanderRow *self) { HdyExpanderRowPrivate *priv; g_return_val_if_fail (HDY_IS_EXPANDER_ROW (self), FALSE); priv = hdy_expander_row_get_instance_private (self); return priv->enable_expansion; } /** * hdy_expander_row_set_enable_expansion: * @self: a #HdyExpanderRow * @enable_expansion: %TRUE to enable the expansion * * Sets whether the expansion of @self is enabled. * * Since: 0.0.6 */ void hdy_expander_row_set_enable_expansion (HdyExpanderRow *self, gboolean enable_expansion) { HdyExpanderRowPrivate *priv; g_return_if_fail (HDY_IS_EXPANDER_ROW (self)); priv = hdy_expander_row_get_instance_private (self); if (priv->enable_expansion == !!enable_expansion) return; priv->enable_expansion = !!enable_expansion; hdy_expander_row_set_expanded (self, priv->enable_expansion); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ENABLE_EXPANSION]); } /** * hdy_expander_row_get_show_enable_switch: * @self: a #HdyExpanderRow * * Gets whether the switch enabling the expansion of @self is visible. * * Returns: whether the switch enabling the expansion of @self is visible. * * Since: 0.0.6 */ gboolean hdy_expander_row_get_show_enable_switch (HdyExpanderRow *self) { HdyExpanderRowPrivate *priv; g_return_val_if_fail (HDY_IS_EXPANDER_ROW (self), FALSE); priv = hdy_expander_row_get_instance_private (self); return priv->show_enable_switch; } /** * hdy_expander_row_set_show_enable_switch: * @self: a #HdyExpanderRow * @show_enable_switch: %TRUE to show the switch enabling the expansion * * Sets whether the switch enabling the expansion of @self is visible. * * Since: 0.0.6 */ void hdy_expander_row_set_show_enable_switch (HdyExpanderRow *self, gboolean show_enable_switch) { HdyExpanderRowPrivate *priv; g_return_if_fail (HDY_IS_EXPANDER_ROW (self)); priv = hdy_expander_row_get_instance_private (self); if (priv->show_enable_switch == !!show_enable_switch) return; priv->show_enable_switch = !!show_enable_switch; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_ENABLE_SWITCH]); } libhandy-0.0.13/src/hdy-expander-row.h000066400000000000000000000023741360136463700175410ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include "hdy-action-row.h" G_BEGIN_DECLS #define HDY_TYPE_EXPANDER_ROW (hdy_expander_row_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyExpanderRow, hdy_expander_row, HDY, EXPANDER_ROW, HdyActionRow) /** * HdyExpanderRowClass * @parent_class: The parent class */ struct _HdyExpanderRowClass { HdyActionRowClass parent_class; }; HdyExpanderRow *hdy_expander_row_new (void); gboolean hdy_expander_row_get_expanded (HdyExpanderRow *self); void hdy_expander_row_set_expanded (HdyExpanderRow *self, gboolean expanded); gboolean hdy_expander_row_get_enable_expansion (HdyExpanderRow *self); void hdy_expander_row_set_enable_expansion (HdyExpanderRow *self, gboolean enable_expansion); gboolean hdy_expander_row_get_show_enable_switch (HdyExpanderRow *self); void hdy_expander_row_set_show_enable_switch (HdyExpanderRow *self, gboolean show_enable_switch); G_END_DECLS libhandy-0.0.13/src/hdy-expander-row.ui000066400000000000000000000040041360136463700177170ustar00rootroot00000000000000 libhandy-0.0.13/src/hdy-fold.c000066400000000000000000000007321360136463700160410ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "hdy-fold.h" /** * SECTION:hdy-fold * @short_description: Element folding states. * @title: HdyFold */ /** * HdyFold: * @HDY_FOLD_UNFOLDED: The element isn't folded * @HDY_FOLD_FOLDED: The element is folded * * Represents the fold of widgets and other objects which can be switched * between folded and unfolded state on the fly, like HdyLeaflet. */ libhandy-0.0.13/src/hdy-fold.h000066400000000000000000000005441360136463700160470ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include "hdy-enums.h" G_BEGIN_DECLS typedef enum { HDY_FOLD_UNFOLDED, HDY_FOLD_FOLDED, } HdyFold; G_END_DECLS libhandy-0.0.13/src/hdy-header-bar.c000066400000000000000000002732121360136463700171140ustar00rootroot00000000000000/* * Copyright (c) 2013 Red Hat, Inc. * Copyright (C) 2019 Purism SPC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-header-bar.h" #include "hdy-animation-private.h" #include "hdy-dialog.h" #include "hdy-enums.h" #include "gtkprogresstrackerprivate.h" #include "gtk-window-private.h" /** * SECTION:hdy-header-bar * @short_description: A box with a centered child. * @Title: HdyHeaderBar * @See_also: #GtkHeaderBar, #HdyTitleBar, #HdyViewSwitcher, #HdyDialog * * HdyHeaderBar is similar to #GtkHeaderBar but is designed to fix some of its * shortcomings for adaptive applications. * * HdyHeaderBar doesn't force the custom title widget to be vertically centered, * hence allowing it to fill up the whole height, which is e.g. needed for * #HdyViewSwitcher. * * When used in a #HdyDialog, HdyHeaderBar will replace its window decorations * by a back button allowing to close it. It doesn't have to be its direct child * and you can use any complex contraption you like as the dialog's titlebar. */ /** * HdyCenteringPolicy: * @HDY_CENTERING_POLICY_LOOSE: Keep the title centered when possible * @HDY_CENTERING_POLICY_STRICT: Keep the title centered at all cost */ #define DEFAULT_SPACING 6 #define MIN_TITLE_CHARS 5 #define MOBILE_WINDOW_WIDTH 400 #define MOBILE_WINDOW_HEIGHT 800 typedef struct { gchar *title; gchar *subtitle; GtkWidget *title_label; GtkWidget *subtitle_label; GtkWidget *label_box; GtkWidget *label_sizing_box; GtkWidget *subtitle_sizing_label; GtkWidget *custom_title; gint spacing; gboolean has_subtitle; GList *children; gboolean shows_wm_decorations; gchar *decoration_layout; gboolean decoration_layout_set; GtkWidget *titlebar_start_box; GtkWidget *titlebar_end_box; GtkWidget *titlebar_start_separator; GtkWidget *titlebar_end_separator; GtkWidget *titlebar_icon; guint tick_id; GtkProgressTracker tracker; gboolean first_frame_skipped; HdyCenteringPolicy centering_policy; guint transition_duration; gboolean interpolate_size; gboolean is_mobile_window; gulong window_size_allocated_id; } HdyHeaderBarPrivate; typedef struct _Child Child; struct _Child { GtkWidget *widget; GtkPackType pack_type; }; enum { PROP_0, PROP_TITLE, PROP_SUBTITLE, PROP_HAS_SUBTITLE, PROP_CUSTOM_TITLE, PROP_SPACING, PROP_SHOW_CLOSE_BUTTON, PROP_DECORATION_LAYOUT, PROP_DECORATION_LAYOUT_SET, PROP_CENTERING_POLICY, PROP_TRANSITION_DURATION, PROP_TRANSITION_RUNNING, PROP_INTERPOLATE_SIZE, LAST_PROP }; enum { CHILD_PROP_0, CHILD_PROP_PACK_TYPE, CHILD_PROP_POSITION }; static GParamSpec *props[LAST_PROP] = { NULL, }; static void hdy_header_bar_buildable_init (GtkBuildableIface *iface); G_DEFINE_TYPE_WITH_CODE (HdyHeaderBar, hdy_header_bar, GTK_TYPE_CONTAINER, G_ADD_PRIVATE (HdyHeaderBar) G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, hdy_header_bar_buildable_init)); static gboolean hdy_header_bar_transition_cb (GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data) { HdyHeaderBar *self = HDY_HEADER_BAR (widget); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); if (priv->first_frame_skipped) gtk_progress_tracker_advance_frame (&priv->tracker, gdk_frame_clock_get_frame_time (frame_clock)); else priv->first_frame_skipped = TRUE; /* Finish the animation early if the widget isn't mapped anymore. */ if (!gtk_widget_get_mapped (widget)) gtk_progress_tracker_finish (&priv->tracker); gtk_widget_queue_resize (widget); if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER) { priv->tick_id = 0; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]); return FALSE; } return TRUE; } static void hdy_header_bar_schedule_ticks (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); if (priv->tick_id == 0) { priv->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), hdy_header_bar_transition_cb, self, NULL); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]); } } static void hdy_header_bar_unschedule_ticks (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); if (priv->tick_id != 0) { gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->tick_id); priv->tick_id = 0; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]); } } static void hdy_header_bar_start_transition (HdyHeaderBar *self, guint transition_duration) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GtkWidget *widget = GTK_WIDGET (self); if (gtk_widget_get_mapped (widget) && priv->interpolate_size && transition_duration != 0) { priv->first_frame_skipped = FALSE; hdy_header_bar_schedule_ticks (self); gtk_progress_tracker_start (&priv->tracker, priv->transition_duration * 1000, 0, 1.0); } else { hdy_header_bar_unschedule_ticks (self); gtk_progress_tracker_finish (&priv->tracker); } gtk_widget_queue_resize (widget); } static void init_sizing_box (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GtkWidget *w; GtkStyleContext *context; /* We use this box to always request size for the two labels (title * and subtitle) as if they were always visible, but then allocate * the real label box with its actual size, to keep it center-aligned * in case we have only the title. */ w = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_widget_show (w); priv->label_sizing_box = g_object_ref_sink (w); w = gtk_label_new (NULL); gtk_widget_show (w); context = gtk_widget_get_style_context (w); gtk_style_context_add_class (context, GTK_STYLE_CLASS_TITLE); gtk_box_pack_start (GTK_BOX (priv->label_sizing_box), w, FALSE, FALSE, 0); gtk_label_set_line_wrap (GTK_LABEL (w), FALSE); gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE); gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_END); gtk_label_set_width_chars (GTK_LABEL (w), MIN_TITLE_CHARS); w = gtk_label_new (NULL); context = gtk_widget_get_style_context (w); gtk_style_context_add_class (context, GTK_STYLE_CLASS_SUBTITLE); gtk_box_pack_start (GTK_BOX (priv->label_sizing_box), w, FALSE, FALSE, 0); gtk_label_set_line_wrap (GTK_LABEL (w), FALSE); gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE); gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_END); gtk_widget_set_visible (w, priv->has_subtitle || (priv->subtitle && priv->subtitle[0])); priv->subtitle_sizing_label = w; } static GtkWidget * create_title_box (const char *title, const char *subtitle, GtkWidget **ret_title_label, GtkWidget **ret_subtitle_label) { GtkWidget *label_box; GtkWidget *title_label; GtkWidget *subtitle_label; GtkStyleContext *context; label_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_widget_set_valign (label_box, GTK_ALIGN_CENTER); gtk_widget_show (label_box); title_label = gtk_label_new (title); context = gtk_widget_get_style_context (title_label); gtk_style_context_add_class (context, GTK_STYLE_CLASS_TITLE); gtk_label_set_line_wrap (GTK_LABEL (title_label), FALSE); gtk_label_set_single_line_mode (GTK_LABEL (title_label), TRUE); gtk_label_set_ellipsize (GTK_LABEL (title_label), PANGO_ELLIPSIZE_END); gtk_box_pack_start (GTK_BOX (label_box), title_label, FALSE, FALSE, 0); gtk_widget_show (title_label); gtk_label_set_width_chars (GTK_LABEL (title_label), MIN_TITLE_CHARS); subtitle_label = gtk_label_new (subtitle); context = gtk_widget_get_style_context (subtitle_label); gtk_style_context_add_class (context, GTK_STYLE_CLASS_SUBTITLE); gtk_label_set_line_wrap (GTK_LABEL (subtitle_label), FALSE); gtk_label_set_single_line_mode (GTK_LABEL (subtitle_label), TRUE); gtk_label_set_ellipsize (GTK_LABEL (subtitle_label), PANGO_ELLIPSIZE_END); gtk_box_pack_start (GTK_BOX (label_box), subtitle_label, FALSE, FALSE, 0); gtk_widget_set_no_show_all (subtitle_label, TRUE); gtk_widget_set_visible (subtitle_label, subtitle && subtitle[0]); if (ret_title_label) *ret_title_label = title_label; if (ret_subtitle_label) *ret_subtitle_label = subtitle_label; return label_box; } static gboolean hdy_header_bar_update_window_icon (HdyHeaderBar *self, GtkWindow *window) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GdkPixbuf *pixbuf; gint scale; if (priv->titlebar_icon == NULL) return FALSE; scale = gtk_widget_get_scale_factor (priv->titlebar_icon); if (GTK_IS_BUTTON (gtk_widget_get_parent (priv->titlebar_icon))) pixbuf = hdy_gtk_window_get_icon_for_size (window, scale * 16); else pixbuf = hdy_gtk_window_get_icon_for_size (window, scale * 20); if (pixbuf) { cairo_surface_t *surface; surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, gtk_widget_get_window (priv->titlebar_icon)); gtk_image_set_from_surface (GTK_IMAGE (priv->titlebar_icon), surface); cairo_surface_destroy (surface); g_object_unref (pixbuf); gtk_widget_show (priv->titlebar_icon); return TRUE; } return FALSE; } static void _hdy_header_bar_update_separator_visibility (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); gboolean have_visible_at_start = FALSE; gboolean have_visible_at_end = FALSE; GList *l; for (l = priv->children; l != NULL; l = l->next) { Child *child = l->data; if (gtk_widget_get_visible (child->widget)) { if (child->pack_type == GTK_PACK_START) have_visible_at_start = TRUE; else have_visible_at_end = TRUE; } } if (priv->titlebar_start_separator != NULL) gtk_widget_set_visible (priv->titlebar_start_separator, have_visible_at_start); if (priv->titlebar_end_separator != NULL) gtk_widget_set_visible (priv->titlebar_end_separator, have_visible_at_end); } static void hdy_header_bar_update_window_buttons (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GtkWidget *widget = GTK_WIDGET (self), *toplevel; GtkWindow *window; GtkTextDirection direction; gchar *layout_desc; gchar **tokens, **t; gint i, j; GMenuModel *menu; gboolean shown_by_shell; gboolean is_sovereign_window; gboolean is_mobile_dialog; toplevel = gtk_widget_get_toplevel (widget); if (!gtk_widget_is_toplevel (toplevel)) return; if (priv->titlebar_start_box) { gtk_widget_unparent (priv->titlebar_start_box); priv->titlebar_start_box = NULL; priv->titlebar_start_separator = NULL; } if (priv->titlebar_end_box) { gtk_widget_unparent (priv->titlebar_end_box); priv->titlebar_end_box = NULL; priv->titlebar_end_separator = NULL; } priv->titlebar_icon = NULL; if (!priv->shows_wm_decorations) return; direction = gtk_widget_get_direction (widget); g_object_get (gtk_widget_get_settings (widget), "gtk-shell-shows-app-menu", &shown_by_shell, "gtk-decoration-layout", &layout_desc, NULL); if (priv->decoration_layout_set) { g_free (layout_desc); layout_desc = g_strdup (priv->decoration_layout); } window = GTK_WINDOW (toplevel); if (!shown_by_shell && gtk_window_get_application (window)) menu = gtk_application_get_app_menu (gtk_window_get_application (window)); else menu = NULL; is_sovereign_window = (!gtk_window_get_modal (window) && gtk_window_get_transient_for (window) == NULL && gtk_window_get_type_hint (window) == GDK_WINDOW_TYPE_HINT_NORMAL); is_mobile_dialog= (priv->is_mobile_window && !is_sovereign_window); tokens = g_strsplit (layout_desc, ":", 2); if (tokens) { for (i = 0; i < 2; i++) { GtkWidget *box; GtkWidget *separator; int n_children = 0; if (tokens[i] == NULL) break; t = g_strsplit (tokens[i], ",", -1); separator = gtk_separator_new (GTK_ORIENTATION_VERTICAL); gtk_widget_set_no_show_all (separator, TRUE); gtk_style_context_add_class (gtk_widget_get_style_context (separator), "titlebutton"); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, priv->spacing); for (j = 0; t[j]; j++) { GtkWidget *button = NULL; GtkWidget *image = NULL; AtkObject *accessible; if (strcmp (t[j], "icon") == 0 && is_sovereign_window) { button = gtk_image_new (); gtk_widget_set_valign (button, GTK_ALIGN_CENTER); priv->titlebar_icon = button; gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton"); gtk_style_context_add_class (gtk_widget_get_style_context (button), "icon"); gtk_widget_set_size_request (button, 20, 20); gtk_widget_show (button); if (!hdy_header_bar_update_window_icon (self, window)) { gtk_widget_destroy (button); priv->titlebar_icon = NULL; button = NULL; } } else if (strcmp (t[j], "menu") == 0 && menu != NULL && is_sovereign_window) { button = gtk_menu_button_new (); gtk_widget_set_valign (button, GTK_ALIGN_CENTER); gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), menu); gtk_menu_button_set_use_popover (GTK_MENU_BUTTON (button), TRUE); gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton"); gtk_style_context_add_class (gtk_widget_get_style_context (button), "appmenu"); image = gtk_image_new (); gtk_container_add (GTK_CONTAINER (button), image); gtk_widget_set_can_focus (button, FALSE); gtk_widget_show_all (button); accessible = gtk_widget_get_accessible (button); if (GTK_IS_ACCESSIBLE (accessible)) atk_object_set_name (accessible, _("Application menu")); priv->titlebar_icon = image; if (!hdy_header_bar_update_window_icon (self, window)) gtk_image_set_from_icon_name (GTK_IMAGE (priv->titlebar_icon), "application-x-executable-symbolic", GTK_ICON_SIZE_MENU); } else if (strcmp (t[j], "minimize") == 0 && is_sovereign_window) { button = gtk_button_new (); gtk_widget_set_valign (button, GTK_ALIGN_CENTER); gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton"); gtk_style_context_add_class (gtk_widget_get_style_context (button), "minimize"); image = gtk_image_new_from_icon_name ("window-minimize-symbolic", GTK_ICON_SIZE_MENU); g_object_set (image, "use-fallback", TRUE, NULL); gtk_container_add (GTK_CONTAINER (button), image); gtk_widget_set_can_focus (button, FALSE); gtk_widget_show_all (button); g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_iconify), window); accessible = gtk_widget_get_accessible (button); if (GTK_IS_ACCESSIBLE (accessible)) atk_object_set_name (accessible, _("Minimize")); } else if (strcmp (t[j], "maximize") == 0 && gtk_window_get_resizable (window) && is_sovereign_window) { const gchar *icon_name; gboolean maximized = gtk_window_is_maximized (window); icon_name = maximized ? "window-restore-symbolic" : "window-maximize-symbolic"; button = gtk_button_new (); gtk_widget_set_valign (button, GTK_ALIGN_CENTER); gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton"); gtk_style_context_add_class (gtk_widget_get_style_context (button), "maximize"); image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU); g_object_set (image, "use-fallback", TRUE, NULL); gtk_container_add (GTK_CONTAINER (button), image); gtk_widget_set_can_focus (button, FALSE); gtk_widget_show_all (button); g_signal_connect_swapped (button, "clicked", G_CALLBACK (hdy_gtk_window_toggle_maximized), window); accessible = gtk_widget_get_accessible (button); if (GTK_IS_ACCESSIBLE (accessible)) atk_object_set_name (accessible, maximized ? _("Restore") : _("Maximize")); } else if (strcmp (t[j], "close") == 0 && gtk_window_get_deletable (window) && !is_mobile_dialog) { button = gtk_button_new (); gtk_widget_set_valign (button, GTK_ALIGN_CENTER); image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_MENU); gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton"); gtk_style_context_add_class (gtk_widget_get_style_context (button), "close"); g_object_set (image, "use-fallback", TRUE, NULL); gtk_container_add (GTK_CONTAINER (button), image); gtk_widget_set_can_focus (button, FALSE); gtk_widget_show_all (button); g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_close), window); accessible = gtk_widget_get_accessible (button); if (GTK_IS_ACCESSIBLE (accessible)) atk_object_set_name (accessible, _("Close")); } else if (i == 0 && /* Only at the start. */ gtk_window_get_deletable (window) && is_mobile_dialog) { button = gtk_button_new (); gtk_widget_set_valign (button, GTK_ALIGN_CENTER); image = gtk_image_new_from_icon_name ("go-previous-symbolic", GTK_ICON_SIZE_BUTTON); g_object_set (image, "use-fallback", TRUE, NULL); gtk_container_add (GTK_CONTAINER (button), image); gtk_widget_set_can_focus (button, TRUE); gtk_widget_show_all (button); g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_close), window); accessible = gtk_widget_get_accessible (button); if (GTK_IS_ACCESSIBLE (accessible)) atk_object_set_name (accessible, _("Back")); } if (button) { gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); n_children ++; } } g_strfreev (t); if (n_children == 0) { g_object_ref_sink (box); g_object_unref (box); g_object_ref_sink (separator); g_object_unref (separator); continue; } gtk_box_pack_start (GTK_BOX (box), separator, FALSE, FALSE, 0); if (i == 1) gtk_box_reorder_child (GTK_BOX (box), separator, 0); if ((direction == GTK_TEXT_DIR_LTR && i == 0) || (direction == GTK_TEXT_DIR_RTL && i == 1)) gtk_style_context_add_class (gtk_widget_get_style_context (box), GTK_STYLE_CLASS_LEFT); else gtk_style_context_add_class (gtk_widget_get_style_context (box), GTK_STYLE_CLASS_RIGHT); gtk_widget_show (box); gtk_widget_set_parent (box, GTK_WIDGET (self)); if (i == 0) { priv->titlebar_start_box = box; priv->titlebar_start_separator = separator; } else { priv->titlebar_end_box = box; priv->titlebar_end_separator = separator; } } g_strfreev (tokens); } g_free (layout_desc); _hdy_header_bar_update_separator_visibility (self); } static gboolean compute_is_mobile_window (GtkWindow *window) { gint window_width, window_height; gtk_window_get_size (window, &window_width, &window_height); if (window_width <= MOBILE_WINDOW_WIDTH && window_height <= MOBILE_WINDOW_HEIGHT) return TRUE; /* Mobile landscape mode. */ if (window_width <= MOBILE_WINDOW_HEIGHT && window_height <= MOBILE_WINDOW_WIDTH && gtk_window_is_maximized (window)) return TRUE; return FALSE; } static void update_is_mobile_window (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self)); gboolean was_mobile_window = priv->is_mobile_window; if (!gtk_widget_is_toplevel (toplevel)) return; priv->is_mobile_window = compute_is_mobile_window (GTK_WINDOW (toplevel)); if (priv->is_mobile_window != was_mobile_window) hdy_header_bar_update_window_buttons (self); } static void construct_label_box (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); g_assert (priv->label_box == NULL); priv->label_box = create_title_box (priv->title, priv->subtitle, &priv->title_label, &priv->subtitle_label); gtk_widget_set_parent (priv->label_box, GTK_WIDGET (self)); } static gint count_visible_children (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GList *l; Child *child; gint n; n = 0; for (l = priv->children; l; l = l->next) { child = l->data; if (gtk_widget_get_visible (child->widget)) n++; } return n; } static gint count_visible_children_for_pack_type (HdyHeaderBar *self, GtkPackType pack_type) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GList *l; Child *child; gint n; n = 0; for (l = priv->children; l; l = l->next) { child = l->data; if (gtk_widget_get_visible (child->widget) && child->pack_type == pack_type) n++; } return n; } static gboolean add_child_size (GtkWidget *child, GtkOrientation orientation, gint *minimum, gint *natural) { gint child_minimum, child_natural; if (!gtk_widget_get_visible (child)) return FALSE; if (orientation == GTK_ORIENTATION_HORIZONTAL) gtk_widget_get_preferred_width (child, &child_minimum, &child_natural); else gtk_widget_get_preferred_height (child, &child_minimum, &child_natural); if (GTK_ORIENTATION_HORIZONTAL == orientation) { *minimum += child_minimum; *natural += child_natural; } else { *minimum = MAX (*minimum, child_minimum); *natural = MAX (*natural, child_natural); } return TRUE; } static void hdy_header_bar_get_size (GtkWidget *widget, GtkOrientation orientation, gint *minimum, gint *natural) { HdyHeaderBar *self = HDY_HEADER_BAR (widget); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GList *l; gint n_start_children = 0, n_end_children = 0, n_center_children = 0; gint start_min = 0, start_nat = 0; gint end_min = 0, end_nat = 0; gint center_min = 0, center_nat = 0; for (l = priv->children; l; l = l->next) { Child *child = l->data; if (child->pack_type == GTK_PACK_START) { if (add_child_size (child->widget, orientation, &start_min, &start_nat)) n_start_children += 1; } else { if (add_child_size (child->widget, orientation, &end_min, &end_nat)) n_end_children += 1; } } if (priv->label_box != NULL) { if (orientation == GTK_ORIENTATION_HORIZONTAL) add_child_size (priv->label_box, orientation, ¢er_min, ¢er_nat); else add_child_size (priv->label_sizing_box, orientation, ¢er_min, ¢er_nat); if (gtk_widget_get_visible (priv->label_sizing_box)) n_center_children += 1; } if (priv->custom_title != NULL) { if (add_child_size (priv->custom_title, orientation, ¢er_min, ¢er_nat)) n_center_children += 1; } if (priv->titlebar_start_box != NULL) { if (add_child_size (priv->titlebar_start_box, orientation, &start_min, &start_nat)) n_start_children += 1; } if (priv->titlebar_end_box != NULL) { if (add_child_size (priv->titlebar_end_box, orientation, &end_min, &end_nat)) n_end_children += 1; } if (orientation == GTK_ORIENTATION_HORIZONTAL) { gdouble strict_centering_t; gint start_min_spaced = start_min + n_start_children * priv->spacing; gint end_min_spaced = end_min + n_end_children * priv->spacing; gint start_nat_spaced = start_nat + n_start_children * priv->spacing; gint end_nat_spaced = end_nat + n_end_children * priv->spacing; if (gtk_progress_tracker_get_state (&priv->tracker) != GTK_PROGRESS_STATE_AFTER) { strict_centering_t = gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE); if (priv->centering_policy != HDY_CENTERING_POLICY_STRICT) strict_centering_t = 1.0 - strict_centering_t; } else strict_centering_t = priv->centering_policy == HDY_CENTERING_POLICY_STRICT ? 1.0 : 0.0; *minimum = center_min + n_start_children * priv->spacing + hdy_lerp (2 * MAX (start_min_spaced, end_min_spaced), start_min_spaced + end_min_spaced, strict_centering_t); *natural = center_nat + n_start_children * priv->spacing + hdy_lerp (2 * MAX (start_nat_spaced, end_nat_spaced), start_nat_spaced + end_nat_spaced, strict_centering_t); } else { *minimum = MAX (MAX (start_min, end_min), center_min); *natural = MAX (MAX (start_nat, end_nat), center_nat); } } static void hdy_header_bar_compute_size_for_orientation (GtkWidget *widget, gint avail_size, gint *minimum_size, gint *natural_size) { HdyHeaderBar *self = HDY_HEADER_BAR (widget); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GList *children; gint required_size = 0; gint required_natural = 0; gint child_size; gint child_natural; gint nvis_children = 0; for (children = priv->children; children != NULL; children = children->next) { Child *child = children->data; if (gtk_widget_get_visible (child->widget)) { gtk_widget_get_preferred_width_for_height (child->widget, avail_size, &child_size, &child_natural); required_size += child_size; required_natural += child_natural; nvis_children += 1; } } if (priv->label_box != NULL) { gtk_widget_get_preferred_width (priv->label_sizing_box, &child_size, &child_natural); required_size += child_size; required_natural += child_natural; } if (priv->custom_title != NULL && gtk_widget_get_visible (priv->custom_title)) { gtk_widget_get_preferred_width (priv->custom_title, &child_size, &child_natural); required_size += child_size; required_natural += child_natural; } if (priv->titlebar_start_box != NULL) { gtk_widget_get_preferred_width (priv->titlebar_start_box, &child_size, &child_natural); required_size += child_size; required_natural += child_natural; nvis_children += 1; } if (priv->titlebar_end_box != NULL) { gtk_widget_get_preferred_width (priv->titlebar_end_box, &child_size, &child_natural); required_size += child_size; required_natural += child_natural; nvis_children += 1; } required_size += nvis_children * priv->spacing; required_natural += nvis_children * priv->spacing; *minimum_size = required_size; *natural_size = required_natural; } static void hdy_header_bar_compute_size_for_opposing_orientation (GtkWidget *widget, gint avail_size, gint *minimum_size, gint *natural_size) { HdyHeaderBar *self = HDY_HEADER_BAR (widget); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); Child *child; GList *children; gint nvis_children; gint computed_minimum = 0; gint computed_natural = 0; GtkRequestedSize *sizes; GtkPackType packing; gint size = 0; gint i; gint child_size; gint child_minimum; gint child_natural; gint center_min, center_nat; nvis_children = count_visible_children (self); if (nvis_children <= 0) return; sizes = g_newa (GtkRequestedSize, nvis_children); /* Retrieve desired size for visible children */ for (i = 0, children = priv->children; children; children = children->next) { child = children->data; if (gtk_widget_get_visible (child->widget)) { gtk_widget_get_preferred_width (child->widget, &sizes[i].minimum_size, &sizes[i].natural_size); size -= sizes[i].minimum_size; sizes[i].data = child; i += 1; } } /* Bring children up to size first */ size = gtk_distribute_natural_allocation (MAX (0, avail_size), nvis_children, sizes); /* Allocate child positions. */ for (packing = GTK_PACK_START; packing <= GTK_PACK_END; ++packing) { for (i = 0, children = priv->children; children; children = children->next) { child = children->data; /* If widget is not visible, skip it. */ if (!gtk_widget_get_visible (child->widget)) continue; /* If widget is packed differently skip it, but still increment i, * since widget is visible and will be handled in next loop * iteration. */ if (child->pack_type != packing) { i++; continue; } child_size = sizes[i].minimum_size; gtk_widget_get_preferred_height_for_width (child->widget, child_size, &child_minimum, &child_natural); computed_minimum = MAX (computed_minimum, child_minimum); computed_natural = MAX (computed_natural, child_natural); } i += 1; } center_min = center_nat = 0; if (priv->label_box != NULL) { gtk_widget_get_preferred_height (priv->label_sizing_box, ¢er_min, ¢er_nat); } if (priv->custom_title != NULL && gtk_widget_get_visible (priv->custom_title)) { gtk_widget_get_preferred_height (priv->custom_title, ¢er_min, ¢er_nat); } if (priv->titlebar_start_box != NULL) { gtk_widget_get_preferred_height (priv->titlebar_start_box, &child_minimum, &child_natural); computed_minimum = MAX (computed_minimum, child_minimum); computed_natural = MAX (computed_natural, child_natural); } if (priv->titlebar_end_box != NULL) { gtk_widget_get_preferred_height (priv->titlebar_end_box, &child_minimum, &child_natural); computed_minimum = MAX (computed_minimum, child_minimum); computed_natural = MAX (computed_natural, child_natural); } *minimum_size = computed_minimum; *natural_size = computed_natural; } static void hdy_header_bar_measure (GtkWidget *widget, GtkOrientation orientation, gint for_size, gint *minimum, gint *natural, gint *minimum_baseline, gint *natural_baseline) { GtkStyleContext *style_context; GtkStateFlags state_flags; GtkBorder border, margin, padding; if (for_size < 0) hdy_header_bar_get_size (widget, orientation, minimum, natural); else if (orientation == GTK_ORIENTATION_HORIZONTAL) hdy_header_bar_compute_size_for_orientation (widget, for_size, minimum, natural); else hdy_header_bar_compute_size_for_opposing_orientation (widget, for_size, minimum, natural); /* Manually apply the border, the padding and the margin as we can't use the * private GtkGagdet. */ style_context = gtk_widget_get_style_context (widget); state_flags = gtk_widget_get_state_flags (widget); gtk_style_context_get_border (style_context, state_flags, &border); gtk_style_context_get_margin (style_context, state_flags, &margin); gtk_style_context_get_padding (style_context, state_flags, &padding); if (orientation == GTK_ORIENTATION_VERTICAL) { *minimum += border.top + margin.top + padding.top + border.bottom + margin.bottom + padding.bottom; *natural += border.top + margin.top + padding.top + border.bottom + margin.bottom + padding.bottom; } else { *minimum += border.left + margin.left + padding.left + border.right + margin.right + padding.right; *natural += border.left + margin.left + padding.left + border.right + margin.right + padding.right; } } static void hdy_header_bar_get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural) { hdy_header_bar_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, minimum, natural, NULL, NULL); } static void hdy_header_bar_get_preferred_height (GtkWidget *widget, gint *minimum, gint *natural) { hdy_header_bar_measure (widget, GTK_ORIENTATION_VERTICAL, -1, minimum, natural, NULL, NULL); } static void hdy_header_bar_get_preferred_width_for_height (GtkWidget *widget, gint height, gint *minimum, gint *natural) { hdy_header_bar_measure (widget, GTK_ORIENTATION_HORIZONTAL, height, minimum, natural, NULL, NULL); } static void hdy_header_bar_get_preferred_height_for_width (GtkWidget *widget, gint width, gint *minimum, gint *natural) { hdy_header_bar_measure (widget, GTK_ORIENTATION_VERTICAL, width, minimum, natural, NULL, NULL); } static GtkWidget * get_title_size (HdyHeaderBar *self, gint for_height, GtkRequestedSize *size, gint *expanded) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GtkWidget *title_widget; if (priv->custom_title != NULL && gtk_widget_get_visible (priv->custom_title)) title_widget = priv->custom_title; else if (priv->label_box != NULL) title_widget = priv->label_box; else return NULL; gtk_widget_get_preferred_width_for_height (title_widget, for_height, &(size->minimum_size), &(size->natural_size)); *expanded = gtk_widget_compute_expand (title_widget, GTK_ORIENTATION_HORIZONTAL); return title_widget; } static void children_allocate (HdyHeaderBar *self, GtkAllocation *allocation, GtkAllocation **allocations, GtkRequestedSize *sizes, gint decoration_width[2], gint uniform_expand_bonus[2], gint leftover_expand_bonus[2]) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GtkPackType packing; GtkAllocation child_allocation; gint x; gint i; GList *l; Child *child; gint child_size; /* GtkTextDirection direction; */ /* Allocate the children on both sides of the title. */ for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) { child_allocation.y = allocation->y; child_allocation.height = allocation->height; if (packing == GTK_PACK_START) x = allocation->x + decoration_width[0]; else x = allocation->x + allocation->width - decoration_width[1]; i = 0; for (l = priv->children; l != NULL; l = l->next) { child = l->data; if (!gtk_widget_get_visible (child->widget)) continue; if (child->pack_type != packing) goto next; child_size = sizes[i].minimum_size; /* If this child is expanded, give it extra space from the reserves. */ if (gtk_widget_compute_expand (child->widget, GTK_ORIENTATION_HORIZONTAL)) { gint expand_bonus; expand_bonus = uniform_expand_bonus[packing]; if (leftover_expand_bonus[packing] > 0) { expand_bonus++; leftover_expand_bonus[packing]--; } child_size += expand_bonus; } child_allocation.width = child_size; if (packing == GTK_PACK_START) { child_allocation.x = x; x += child_size; x += priv->spacing; } else { x -= child_size; child_allocation.x = x; x -= priv->spacing; } if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width; (*allocations)[i] = child_allocation; next: i++; } } } static void get_loose_centering_allocations (HdyHeaderBar *self, GtkAllocation *allocation, GtkAllocation **allocations, GtkAllocation *title_allocation, gint decoration_width[2]) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GtkRequestedSize *sizes; gint width; gint nvis_children; GtkRequestedSize title_size = { 0 }; gboolean title_expands = FALSE; gint side[2] = { 0 }; gint uniform_expand_bonus[2] = { 0 }; gint leftover_expand_bonus[2] = { 0 }; gint side_free_space[2] = { 0 }; gint center_free_space[2] = { 0 }; gint nexpand_children[2] = { 0 }; gint center_free_space_min; GList *l; gint i; Child *child; GtkPackType packing; nvis_children = count_visible_children (self); sizes = g_newa (GtkRequestedSize, nvis_children); width = allocation->width - nvis_children * priv->spacing; i = 0; for (l = priv->children; l; l = l->next) { child = l->data; if (!gtk_widget_get_visible (child->widget)) continue; if (gtk_widget_compute_expand (child->widget, GTK_ORIENTATION_HORIZONTAL)) nexpand_children[child->pack_type]++; gtk_widget_get_preferred_width_for_height (child->widget, allocation->height, &sizes[i].minimum_size, &sizes[i].natural_size); width -= sizes[i].minimum_size; i++; } get_title_size (self, allocation->height, &title_size, &title_expands); width -= title_size.minimum_size; /* Compute the nominal size of the children filling up each side of the title * in titlebar. */ for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) { i = 0; for (l = priv->children; l != NULL; l = l->next) { child = l->data; if (!gtk_widget_get_visible (child->widget)) continue; if (child->pack_type == packing) side[packing] += sizes[i].minimum_size + priv->spacing; i++; } } /* Distribute the available space for natural expansion of the children. */ for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) width -= decoration_width[packing]; width = gtk_distribute_natural_allocation (MAX (0, width), 1, &title_size); width = gtk_distribute_natural_allocation (MAX (0, width), nvis_children, sizes); /* Compute the nominal size of the children filling up each side of the title * in titlebar. */ for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) { i = 0; side[packing] = 0; for (l = priv->children; l != NULL; l = l->next) { child = l->data; if (!gtk_widget_get_visible (child->widget)) continue; if (child->pack_type == packing) side[packing] += sizes[i].minimum_size + priv->spacing; i++; } } /* Figure out how much space is left on each side of the title, and earkmark * that space for the expanded children. * * If the title itself is expanded, then it gets half the spoils from each * side. */ for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) { side_free_space[packing] = MIN (MAX (allocation->width / 2 - title_size.natural_size / 2 - decoration_width[packing] - side[packing], 0), width); if (title_expands) center_free_space[packing] = nexpand_children[packing] > 0 ? side_free_space[packing] / 2 : side_free_space[packing]; } center_free_space_min = MIN (center_free_space[0], center_free_space[1]); for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) { center_free_space[packing] = center_free_space_min; side_free_space[packing] -= center_free_space[packing]; width -= side_free_space[packing]; if (nexpand_children[packing] == 0) continue; uniform_expand_bonus[packing] = (side_free_space[packing]) / nexpand_children[packing]; leftover_expand_bonus[packing] = (side_free_space[packing]) % nexpand_children[packing]; } children_allocate (self, allocation, allocations, sizes, decoration_width, uniform_expand_bonus, leftover_expand_bonus); /* We don't enforce css borders on the center widget, to make title/subtitle * combinations fit without growing the header. */ title_allocation->y = allocation->y; title_allocation->height = allocation->height; title_allocation->width = MIN (allocation->width - decoration_width[0] - side[0] - decoration_width[1] - side[1], title_size.natural_size); title_allocation->x = allocation->x + (allocation->width - title_allocation->width) / 2; /* If the title widget is expanded, then grow it by all the available free * space, and recenter it. */ if (title_expands && width > 0) { title_allocation->width += width; title_allocation->x -= width / 2; } if (allocation->x + decoration_width[0] + side[0] > title_allocation->x) title_allocation->x = allocation->x + decoration_width[0] + side[0]; else if (allocation->x + allocation->width - decoration_width[1] - side[1] < title_allocation->x + title_allocation->width) title_allocation->x = allocation->x + allocation->width - decoration_width[1] - side[1] - title_allocation->width; if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) title_allocation->x = allocation->x + allocation->width - (title_allocation->x - allocation->x) - title_allocation->width; } static void get_strict_centering_allocations (HdyHeaderBar *self, GtkAllocation *allocation, GtkAllocation **allocations, GtkAllocation *title_allocation, gint decoration_width[2]) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GtkRequestedSize *children_sizes = { 0 }; GtkRequestedSize *children_sizes_for_side[2] = { 0 }; GtkRequestedSize side_size[2] = { 0 }; /* The size requested by each side. */ GtkRequestedSize title_size = { 0 }; /* The size requested by the title. */ GtkRequestedSize side_request = { 0 }; /* The maximum size requested by each side, decoration included. */ gint side_max; /* The maximum space allocatable to each side, decoration included. */ gint title_leftover; /* The or 0px or 1px leftover from ensuring each side is allocated the same size. */ /* The space available for expansion on each side, including for the title. */ gint free_space[2] = { 0 }; /* The space the title will take from the free space of each side for its expansion. */ gint title_expand_bonus = 0; gint uniform_expand_bonus[2] = { 0 }; gint leftover_expand_bonus[2] = { 0 }; gint nvis_children, n_side_vis_children[2] = { 0 }; gint nexpand_children[2] = { 0 }; gboolean title_expands = FALSE; GList *l; gint i; Child *child; GtkPackType packing; get_title_size (self, allocation->height, &title_size, &title_expands); nvis_children = count_visible_children (self); children_sizes = g_newa (GtkRequestedSize, nvis_children); for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) { n_side_vis_children[packing] = count_visible_children_for_pack_type (self, packing); children_sizes_for_side[packing] = packing == 0 ? children_sizes : children_sizes + n_side_vis_children[packing - 1]; free_space[packing] = (allocation->width - title_size.minimum_size) / 2 - decoration_width[packing]; } /* Compute the nominal size of the children filling up each side of the title * in titlebar. */ i = 0; for (l = priv->children; l; l = l->next) { child = l->data; if (!gtk_widget_get_visible (child->widget)) continue; if (gtk_widget_compute_expand (child->widget, GTK_ORIENTATION_HORIZONTAL)) nexpand_children[child->pack_type]++; gtk_widget_get_preferred_width_for_height (child->widget, allocation->height, &children_sizes[i].minimum_size, &children_sizes[i].natural_size); side_size[child->pack_type].minimum_size += children_sizes[i].minimum_size + priv->spacing; side_size[child->pack_type].natural_size += children_sizes[i].natural_size + priv->spacing; free_space[child->pack_type] -= children_sizes[i].minimum_size + priv->spacing; i++; } /* Figure out the space maximum size requests from each side to help centering * the title. */ side_request.minimum_size = MAX (side_size[GTK_PACK_START].minimum_size + decoration_width[GTK_PACK_START], side_size[GTK_PACK_END].minimum_size + decoration_width[GTK_PACK_END]); side_request.natural_size = MAX (side_size[GTK_PACK_START].natural_size + decoration_width[GTK_PACK_START], side_size[GTK_PACK_END].natural_size + decoration_width[GTK_PACK_END]); title_leftover = (allocation->width - title_size.natural_size) % 2; side_max = MAX ((allocation->width - title_size.natural_size) / 2, side_request.minimum_size); /* Distribute the available space for natural expansion of the children and * figure out how much space is left on each side of the title, free to be * used for expansion. */ for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) { gint leftovers = side_max - side_size[packing].minimum_size - decoration_width[packing]; free_space[packing] = gtk_distribute_natural_allocation (leftovers, n_side_vis_children[packing], children_sizes_for_side[packing]); } /* Compute how much of each side's free space should be distributed to the * title for its expansion. */ title_expand_bonus = !title_expands ? 0 : MIN (nexpand_children[GTK_PACK_START] > 0 ? free_space[GTK_PACK_START] / 2 : free_space[GTK_PACK_START], nexpand_children[GTK_PACK_END] > 0 ? free_space[GTK_PACK_END] / 2 : free_space[GTK_PACK_END]); /* Remove the space the title takes from each side for its expansion. */ for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) free_space[packing] -= title_expand_bonus; /* Distribute the free space for expansion of the children. */ for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) { if (nexpand_children[packing] == 0) continue; uniform_expand_bonus[packing] = free_space[packing] / nexpand_children[packing]; leftover_expand_bonus[packing] = free_space[packing] % nexpand_children[packing]; } children_allocate (self, allocation, allocations, children_sizes, decoration_width, uniform_expand_bonus, leftover_expand_bonus); /* We don't enforce css borders on the center widget, to make title/subtitle * combinations fit without growing the header. */ title_allocation->y = allocation->y; title_allocation->height = allocation->height; title_allocation->width = MIN (allocation->width - 2 * side_max + title_leftover, title_size.natural_size); title_allocation->x = allocation->x + (allocation->width - title_allocation->width) / 2; /* If the title widget is expanded, then grow it by the free space available * for it. */ if (title_expands) { title_allocation->width += 2 * title_expand_bonus; title_allocation->x -= title_expand_bonus; } if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) title_allocation->x = allocation->x + allocation->width - (title_allocation->x - allocation->x) - title_allocation->width; } static void hdy_header_bar_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { HdyHeaderBar *self = HDY_HEADER_BAR (widget); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GtkAllocation *allocations; GtkAllocation title_allocation; gint nvis_children; GList *l; gint i; Child *child; GtkAllocation child_allocation; GtkTextDirection direction; GtkStyleContext *style_context; GtkStateFlags state_flags; GtkBorder border, margin, padding; GtkWidget *decoration_box[2] = { priv->titlebar_start_box, priv->titlebar_end_box }; gint decoration_width[2] = { 0 }; gtk_widget_set_allocation (widget, allocation); /* Manually apply the border, the padding and the margin as we can't use the * private GtkGagdet. */ style_context = gtk_widget_get_style_context (widget); state_flags = gtk_widget_get_state_flags (widget); gtk_style_context_get_border (style_context, state_flags, &border); gtk_style_context_get_margin (style_context, state_flags, &margin); gtk_style_context_get_padding (style_context, state_flags, &padding); allocation->width -= border.left + border.right + margin.left + margin.right + padding.left + padding.right; allocation->height -= border.top + border.bottom + margin.top + margin.bottom + padding.top + padding.bottom; allocation->x += border.left + margin.left + padding.left; allocation->y += border.top + margin.top + padding.top; direction = gtk_widget_get_direction (widget); nvis_children = count_visible_children (self); allocations = g_newa (GtkAllocation, nvis_children); /* Get the decoration width. */ for (GtkPackType packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) { gint min, nat; if (decoration_box[packing] == NULL) continue; gtk_widget_get_preferred_width_for_height (decoration_box[packing], allocation->height, &min, &nat); decoration_width[packing] = nat + priv->spacing; } /* Allocate the decoration widgets. */ child_allocation.y = allocation->y; child_allocation.height = allocation->height; if (priv->titlebar_start_box) { if (direction == GTK_TEXT_DIR_LTR) child_allocation.x = allocation->x; else child_allocation.x = allocation->x + allocation->width - decoration_width[GTK_PACK_START] + priv->spacing; child_allocation.width = decoration_width[GTK_PACK_START] - priv->spacing; gtk_widget_size_allocate (priv->titlebar_start_box, &child_allocation); } if (priv->titlebar_end_box) { if (direction != GTK_TEXT_DIR_LTR) child_allocation.x = allocation->x; else child_allocation.x = allocation->x + allocation->width - decoration_width[GTK_PACK_END] + priv->spacing; child_allocation.width = decoration_width[GTK_PACK_END] - priv->spacing; gtk_widget_size_allocate (priv->titlebar_end_box, &child_allocation); } /* Get the allocation for widgets on both side of the title. */ if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER) { if (priv->centering_policy == HDY_CENTERING_POLICY_STRICT) get_strict_centering_allocations (self, allocation, &allocations, &title_allocation, decoration_width); else get_loose_centering_allocations (self, allocation, &allocations, &title_allocation, decoration_width); } else { /* For memory usage optimisation's sake, we will use the allocations * variable to store the loose centering allocations and the * title_allocation variable to store the loose title allocation. */ GtkAllocation *strict_allocations = g_newa (GtkAllocation, nvis_children); GtkAllocation strict_title_allocation; gdouble strict_centering_t = gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE); if (priv->centering_policy != HDY_CENTERING_POLICY_STRICT) strict_centering_t = 1.0 - strict_centering_t; get_loose_centering_allocations (self, allocation, &allocations, &title_allocation, decoration_width); get_strict_centering_allocations (self, allocation, &strict_allocations, &strict_title_allocation, decoration_width); for (i = 0; i < nvis_children; i++) { allocations[i].x = hdy_lerp (strict_allocations[i].x, allocations[i].x, strict_centering_t); allocations[i].y = hdy_lerp (strict_allocations[i].y, allocations[i].y, strict_centering_t); allocations[i].width = hdy_lerp (strict_allocations[i].width, allocations[i].width, strict_centering_t); allocations[i].height = hdy_lerp (strict_allocations[i].height, allocations[i].height, strict_centering_t); } title_allocation.x = hdy_lerp (strict_title_allocation.x, title_allocation.x, strict_centering_t); title_allocation.y = hdy_lerp (strict_title_allocation.y, title_allocation.y, strict_centering_t); title_allocation.width = hdy_lerp (strict_title_allocation.width, title_allocation.width, strict_centering_t); title_allocation.height = hdy_lerp (strict_title_allocation.height, title_allocation.height, strict_centering_t); } /* Allocate the children on both sides of the title. */ i = 0; for (l = priv->children; l != NULL; l = l->next) { child = l->data; if (!gtk_widget_get_visible (child->widget)) continue; gtk_widget_size_allocate (child->widget, &allocations[i]); i++; } /* Allocate the title widget. */ if (priv->custom_title != NULL && gtk_widget_get_visible (priv->custom_title)) gtk_widget_size_allocate (priv->custom_title, &title_allocation); else if (priv->label_box != NULL) gtk_widget_size_allocate (priv->label_box, &title_allocation); } static void hdy_header_bar_destroy (GtkWidget *widget) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (HDY_HEADER_BAR (widget)); if (priv->label_sizing_box) { gtk_widget_destroy (priv->label_sizing_box); g_clear_object (&priv->label_sizing_box); } if (priv->custom_title) { gtk_widget_unparent (priv->custom_title); priv->custom_title = NULL; } if (priv->label_box) { gtk_widget_unparent (priv->label_box); priv->label_box = NULL; } if (priv->titlebar_start_box) { gtk_widget_unparent (priv->titlebar_start_box); priv->titlebar_start_box = NULL; priv->titlebar_start_separator = NULL; } if (priv->titlebar_end_box) { gtk_widget_unparent (priv->titlebar_end_box); priv->titlebar_end_box = NULL; priv->titlebar_end_separator = NULL; } GTK_WIDGET_CLASS (hdy_header_bar_parent_class)->destroy (widget); } static void hdy_header_bar_finalize (GObject *object) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (HDY_HEADER_BAR (object)); g_clear_pointer (&priv->title, g_free); g_clear_pointer (&priv->subtitle, g_free); g_clear_pointer (&priv->decoration_layout, g_free); G_OBJECT_CLASS (hdy_header_bar_parent_class)->finalize (object); } static void hdy_header_bar_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyHeaderBar *self = HDY_HEADER_BAR (object); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); switch (prop_id) { case PROP_TITLE: g_value_set_string (value, priv->title); break; case PROP_SUBTITLE: g_value_set_string (value, priv->subtitle); break; case PROP_CUSTOM_TITLE: g_value_set_object (value, priv->custom_title); break; case PROP_SPACING: g_value_set_int (value, priv->spacing); break; case PROP_SHOW_CLOSE_BUTTON: g_value_set_boolean (value, hdy_header_bar_get_show_close_button (self)); break; case PROP_HAS_SUBTITLE: g_value_set_boolean (value, hdy_header_bar_get_has_subtitle (self)); break; case PROP_DECORATION_LAYOUT: g_value_set_string (value, hdy_header_bar_get_decoration_layout (self)); break; case PROP_DECORATION_LAYOUT_SET: g_value_set_boolean (value, priv->decoration_layout_set); break; case PROP_CENTERING_POLICY: g_value_set_enum (value, hdy_header_bar_get_centering_policy (self)); break; case PROP_TRANSITION_DURATION: g_value_set_uint (value, hdy_header_bar_get_transition_duration (self)); break; case PROP_TRANSITION_RUNNING: g_value_set_boolean (value, hdy_header_bar_get_transition_running (self)); break; case PROP_INTERPOLATE_SIZE: g_value_set_boolean (value, hdy_header_bar_get_interpolate_size (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void hdy_header_bar_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyHeaderBar *self = HDY_HEADER_BAR (object); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); switch (prop_id) { case PROP_TITLE: hdy_header_bar_set_title (self, g_value_get_string (value)); break; case PROP_SUBTITLE: hdy_header_bar_set_subtitle (self, g_value_get_string (value)); break; case PROP_CUSTOM_TITLE: hdy_header_bar_set_custom_title (self, g_value_get_object (value)); break; case PROP_SPACING: if (priv->spacing != g_value_get_int (value)) { priv->spacing = g_value_get_int (value); gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify_by_pspec (object, pspec); } break; case PROP_SHOW_CLOSE_BUTTON: hdy_header_bar_set_show_close_button (self, g_value_get_boolean (value)); break; case PROP_HAS_SUBTITLE: hdy_header_bar_set_has_subtitle (self, g_value_get_boolean (value)); break; case PROP_DECORATION_LAYOUT: hdy_header_bar_set_decoration_layout (self, g_value_get_string (value)); break; case PROP_DECORATION_LAYOUT_SET: priv->decoration_layout_set = g_value_get_boolean (value); break; case PROP_CENTERING_POLICY: hdy_header_bar_set_centering_policy (self, g_value_get_enum (value)); break; case PROP_TRANSITION_DURATION: hdy_header_bar_set_transition_duration (self, g_value_get_uint (value)); break; case PROP_INTERPOLATE_SIZE: hdy_header_bar_set_interpolate_size (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void notify_child_cb (GObject *child, GParamSpec *pspec, HdyHeaderBar *self) { _hdy_header_bar_update_separator_visibility (self); } static void hdy_header_bar_pack (HdyHeaderBar *self, GtkWidget *widget, GtkPackType pack_type) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); Child *child; g_return_if_fail (gtk_widget_get_parent (widget) == NULL); child = g_new (Child, 1); child->widget = widget; child->pack_type = pack_type; priv->children = g_list_append (priv->children, child); gtk_widget_freeze_child_notify (widget); gtk_widget_set_parent (widget, GTK_WIDGET (self)); g_signal_connect (widget, "notify::visible", G_CALLBACK (notify_child_cb), self); gtk_widget_child_notify (widget, "pack-type"); gtk_widget_child_notify (widget, "position"); gtk_widget_thaw_child_notify (widget); _hdy_header_bar_update_separator_visibility (self); } static void hdy_header_bar_add (GtkContainer *container, GtkWidget *child) { hdy_header_bar_pack (HDY_HEADER_BAR (container), child, GTK_PACK_START); } static GList * find_child_link (HdyHeaderBar *self, GtkWidget *widget, gint *position) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GList *l; Child *child; gint i; for (l = priv->children, i = 0; l; l = l->next, i++) { child = l->data; if (child->widget == widget) { if (position) *position = i; return l; } } return NULL; } static void hdy_header_bar_remove (GtkContainer *container, GtkWidget *widget) { HdyHeaderBar *self = HDY_HEADER_BAR (container); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GList *l; Child *child; l = find_child_link (self, widget, NULL); if (l) { child = l->data; g_signal_handlers_disconnect_by_func (widget, notify_child_cb, self); gtk_widget_unparent (child->widget); priv->children = g_list_delete_link (priv->children, l); g_free (child); gtk_widget_queue_resize (GTK_WIDGET (container)); _hdy_header_bar_update_separator_visibility (self); } } static void hdy_header_bar_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { HdyHeaderBar *self = HDY_HEADER_BAR (container); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); Child *child; GList *children; if (include_internals && priv->titlebar_start_box != NULL) (* callback) (priv->titlebar_start_box, callback_data); children = priv->children; while (children) { child = children->data; children = children->next; if (child->pack_type == GTK_PACK_START) (* callback) (child->widget, callback_data); } if (priv->custom_title != NULL) (* callback) (priv->custom_title, callback_data); if (include_internals && priv->label_box != NULL) (* callback) (priv->label_box, callback_data); children = priv->children; while (children) { child = children->data; children = children->next; if (child->pack_type == GTK_PACK_END) (* callback) (child->widget, callback_data); } if (include_internals && priv->titlebar_end_box != NULL) (* callback) (priv->titlebar_end_box, callback_data); } static void hdy_header_bar_reorder_child (HdyHeaderBar *self, GtkWidget *widget, gint position) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GList *l; gint old_position; Child *child; l = find_child_link (self, widget, &old_position); if (l == NULL) return; if (old_position == position) return; child = l->data; priv->children = g_list_delete_link (priv->children, l); if (position < 0) l = NULL; else l = g_list_nth (priv->children, position); priv->children = g_list_insert_before (priv->children, l, child); gtk_widget_child_notify (widget, "position"); gtk_widget_queue_resize (widget); } static GType hdy_header_bar_child_type (GtkContainer *container) { return GTK_TYPE_WIDGET; } static void hdy_header_bar_get_child_property (GtkContainer *container, GtkWidget *widget, guint property_id, GValue *value, GParamSpec *pspec) { HdyHeaderBar *self = HDY_HEADER_BAR (container); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); GList *l; Child *child; l = find_child_link (self, widget, NULL); if (l == NULL) { g_param_value_set_default (pspec, value); return; } child = l->data; switch (property_id) { case CHILD_PROP_PACK_TYPE: g_value_set_enum (value, child->pack_type); break; case CHILD_PROP_POSITION: g_value_set_int (value, g_list_position (priv->children, l)); break; default: GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); break; } } static void hdy_header_bar_set_child_property (GtkContainer *container, GtkWidget *widget, guint property_id, const GValue *value, GParamSpec *pspec) { HdyHeaderBar *self = HDY_HEADER_BAR (container); GList *l; Child *child; l = find_child_link (self, widget, NULL); if (l == NULL) return; child = l->data; switch (property_id) { case CHILD_PROP_PACK_TYPE: child->pack_type = g_value_get_enum (value); _hdy_header_bar_update_separator_visibility (self); gtk_widget_queue_resize (widget); break; case CHILD_PROP_POSITION: hdy_header_bar_reorder_child (self, widget, g_value_get_int (value)); break; default: GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); break; } } static gboolean hdy_header_bar_draw (GtkWidget *widget, cairo_t *cr) { GtkStyleContext *context; context = gtk_widget_get_style_context (widget); /* GtkWidget draws nothing by default so we have to render the background * explicitely for HdyHederBar to render the typical titlebar background. */ gtk_render_background (context, cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget)); /* Ditto for the borders. */ gtk_render_frame (context, cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget)); return GTK_WIDGET_CLASS (hdy_header_bar_parent_class)->draw (widget, cr); } static void hdy_header_bar_realize (GtkWidget *widget) { GtkSettings *settings; GTK_WIDGET_CLASS (hdy_header_bar_parent_class)->realize (widget); settings = gtk_widget_get_settings (widget); g_signal_connect_swapped (settings, "notify::gtk-shell-shows-app-menu", G_CALLBACK (hdy_header_bar_update_window_buttons), widget); g_signal_connect_swapped (settings, "notify::gtk-decoration-layout", G_CALLBACK (hdy_header_bar_update_window_buttons), widget); update_is_mobile_window (HDY_HEADER_BAR (widget)); hdy_header_bar_update_window_buttons (HDY_HEADER_BAR (widget)); } static void hdy_header_bar_unrealize (GtkWidget *widget) { GtkSettings *settings; settings = gtk_widget_get_settings (widget); g_signal_handlers_disconnect_by_func (settings, hdy_header_bar_update_window_buttons, widget); GTK_WIDGET_CLASS (hdy_header_bar_parent_class)->unrealize (widget); } static gboolean window_state_changed (GtkWidget *window, GdkEventWindowState *event, gpointer data) { HdyHeaderBar *self = HDY_HEADER_BAR (data); if (event->changed_mask & (GDK_WINDOW_STATE_FULLSCREEN | GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_TILED | GDK_WINDOW_STATE_TOP_TILED | GDK_WINDOW_STATE_RIGHT_TILED | GDK_WINDOW_STATE_BOTTOM_TILED | GDK_WINDOW_STATE_LEFT_TILED)) hdy_header_bar_update_window_buttons (self); return FALSE; } static void hdy_header_bar_hierarchy_changed (GtkWidget *widget, GtkWidget *previous_toplevel) { GtkWidget *toplevel; HdyHeaderBar *self = HDY_HEADER_BAR (widget); HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); toplevel = gtk_widget_get_toplevel (widget); if (previous_toplevel) g_signal_handlers_disconnect_by_func (previous_toplevel, window_state_changed, widget); if (toplevel) g_signal_connect_after (toplevel, "window-state-event", G_CALLBACK (window_state_changed), widget); if (priv->window_size_allocated_id > 0) { g_signal_handler_disconnect (previous_toplevel, priv->window_size_allocated_id); priv->window_size_allocated_id = 0; } if (GTK_IS_WINDOW (toplevel)) priv->window_size_allocated_id = g_signal_connect_swapped (toplevel, "size-allocate", G_CALLBACK (update_is_mobile_window), self); update_is_mobile_window (self); hdy_header_bar_update_window_buttons (self); } static void hdy_header_bar_class_init (HdyHeaderBarClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class); object_class->finalize = hdy_header_bar_finalize; object_class->get_property = hdy_header_bar_get_property; object_class->set_property = hdy_header_bar_set_property; widget_class->destroy = hdy_header_bar_destroy; widget_class->size_allocate = hdy_header_bar_size_allocate; widget_class->get_preferred_width = hdy_header_bar_get_preferred_width; widget_class->get_preferred_height = hdy_header_bar_get_preferred_height; widget_class->get_preferred_height_for_width = hdy_header_bar_get_preferred_height_for_width; widget_class->get_preferred_width_for_height = hdy_header_bar_get_preferred_width_for_height; widget_class->draw = hdy_header_bar_draw; widget_class->realize = hdy_header_bar_realize; widget_class->unrealize = hdy_header_bar_unrealize; widget_class->hierarchy_changed = hdy_header_bar_hierarchy_changed; container_class->add = hdy_header_bar_add; container_class->remove = hdy_header_bar_remove; container_class->forall = hdy_header_bar_forall; container_class->child_type = hdy_header_bar_child_type; container_class->set_child_property = hdy_header_bar_set_child_property; container_class->get_child_property = hdy_header_bar_get_child_property; gtk_container_class_handle_border_width (container_class); gtk_container_class_install_child_property (container_class, CHILD_PROP_PACK_TYPE, g_param_spec_enum ("pack-type", _("Pack type"), _("A GtkPackType indicating whether the child is packed with reference to the start or end of the parent"), GTK_TYPE_PACK_TYPE, GTK_PACK_START, G_PARAM_READWRITE)); gtk_container_class_install_child_property (container_class, CHILD_PROP_POSITION, g_param_spec_int ("position", _("Position"), _("The index of the child in the parent"), -1, G_MAXINT, 0, G_PARAM_READWRITE)); props[PROP_TITLE] = g_param_spec_string ("title", _("Title"), _("The title to display"), NULL, G_PARAM_READWRITE); props[PROP_SUBTITLE] = g_param_spec_string ("subtitle", _("Subtitle"), _("The subtitle to display"), NULL, G_PARAM_READWRITE); props[PROP_CUSTOM_TITLE] = g_param_spec_object ("custom-title", _("Custom Title"), _("Custom title widget to display"), GTK_TYPE_WIDGET, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); props[PROP_SPACING] = g_param_spec_int ("spacing", _("Spacing"), _("The amount of space between children"), 0, G_MAXINT, DEFAULT_SPACING, G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); /** * HdyHeaderBar:show-close-button: * * Whether to show window decorations. * * Which buttons are actually shown and where is determined * by the #HdyHeaderBar:decoration-layout property, and by * the state of the window (e.g. a close button will not be * shown if the window can't be closed). * * Since: 0.0.10 */ props[PROP_SHOW_CLOSE_BUTTON] = g_param_spec_boolean ("show-close-button", _("Show decorations"), _("Whether to show window decorations"), FALSE, G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); /** * HdyHeaderBar:decoration-layout: * * The decoration layout for buttons. If this property is * not set, the #GtkSettings:gtk-decoration-layout setting * is used. * * See hdy_header_bar_set_decoration_layout() for information * about the format of this string. * * Since: 0.0.10 */ props[PROP_DECORATION_LAYOUT] = g_param_spec_string ("decoration-layout", _("Decoration Layout"), _("The layout for window decorations"), NULL, G_PARAM_READWRITE); /** * HdyHeaderBar:decoration-layout-set: * * Set to %TRUE if #HdyHeaderBar:decoration-layout is set. * * Since: 0.0.10 */ props[PROP_DECORATION_LAYOUT_SET] = g_param_spec_boolean ("decoration-layout-set", _("Decoration Layout Set"), _("Whether the decoration-layout property has been set"), FALSE, G_PARAM_READWRITE); /** * HdyHeaderBar:has-subtitle: * * If %TRUE, reserve space for a subtitle, even if none * is currently set. * * Since: 0.0.10 */ props[PROP_HAS_SUBTITLE] = g_param_spec_boolean ("has-subtitle", _("Has Subtitle"), _("Whether to reserve space for a subtitle"), TRUE, G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); props[PROP_CENTERING_POLICY] = g_param_spec_enum ("centering-policy", _("Centering policy"), _("The policy to horizontally align the center widget"), HDY_TYPE_CENTERING_POLICY, HDY_CENTERING_POLICY_LOOSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_TRANSITION_DURATION] = g_param_spec_uint ("transition-duration", _("Transition duration"), _("The animation duration, in milliseconds"), 0, G_MAXUINT, 200, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_TRANSITION_RUNNING] = g_param_spec_boolean ("transition-running", _("Transition running"), _("Whether or not the transition is currently running"), FALSE, G_PARAM_READABLE); props[PROP_INTERPOLATE_SIZE] = g_param_spec_boolean ("interpolate-size", _("Interpolate size"), _("Whether or not the size should smoothly change when changing between differently sized children"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_PANEL); gtk_widget_class_set_css_name (widget_class, "headerbar"); } static void hdy_header_bar_init (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv; priv = hdy_header_bar_get_instance_private (self); gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); priv->title = NULL; priv->subtitle = NULL; priv->custom_title = NULL; priv->children = NULL; priv->spacing = DEFAULT_SPACING; priv->has_subtitle = TRUE; priv->decoration_layout = NULL; priv->decoration_layout_set = FALSE; priv->transition_duration = 200; init_sizing_box (self); construct_label_box (self); } static void hdy_header_bar_buildable_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *type) { if (type && strcmp (type, "title") == 0) hdy_header_bar_set_custom_title (HDY_HEADER_BAR (buildable), GTK_WIDGET (child)); else if (!type) gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child)); else GTK_BUILDER_WARN_INVALID_CHILD_TYPE (HDY_HEADER_BAR (buildable), type); } static void hdy_header_bar_buildable_init (GtkBuildableIface *iface) { iface->add_child = hdy_header_bar_buildable_add_child; } /** * hdy_header_bar_new: * * Creates a new #HdyHeaderBar widget. * * Returns: a new #HdyHeaderBar * * Since: 0.0.10 */ GtkWidget * hdy_header_bar_new (void) { return GTK_WIDGET (g_object_new (HDY_TYPE_HEADER_BAR, NULL)); } /** * hdy_header_bar_pack_start: * @self: A #HdyHeaderBar * @child: the #GtkWidget to be added to @self: * * Adds @child to @self:, packed with reference to the * start of the @self:. * * Since: 0.0.10 */ void hdy_header_bar_pack_start (HdyHeaderBar *self, GtkWidget *child) { hdy_header_bar_pack (self, child, GTK_PACK_START); } /** * hdy_header_bar_pack_end: * @self: A #HdyHeaderBar * @child: the #GtkWidget to be added to @self: * * Adds @child to @self:, packed with reference to the * end of the @self:. * * Since: 0.0.10 */ void hdy_header_bar_pack_end (HdyHeaderBar *self, GtkWidget *child) { hdy_header_bar_pack (self, child, GTK_PACK_END); } /** * hdy_header_bar_set_title: * @self: a #HdyHeaderBar * @title: (allow-none): a title, or %NULL * * Sets the title of the #HdyHeaderBar. The title should help a user * identify the current view. A good title should not include the * application name. * * Since: 0.0.10 */ void hdy_header_bar_set_title (HdyHeaderBar *self, const gchar *title) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); gchar *new_title; g_return_if_fail (HDY_IS_HEADER_BAR (self)); new_title = g_strdup (title); g_free (priv->title); priv->title = new_title; if (priv->title_label != NULL) { gtk_label_set_label (GTK_LABEL (priv->title_label), priv->title); gtk_widget_queue_resize (GTK_WIDGET (self)); } g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]); } /** * hdy_header_bar_get_title: * @self: a #HdyHeaderBar * * Retrieves the title of the header. See hdy_header_bar_set_title(). * * Returns: (nullable): the title of the header, or %NULL if none has * been set explicitly. The returned string is owned by the widget * and must not be modified or freed. * * Since: 0.0.10 */ const gchar * hdy_header_bar_get_title (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); g_return_val_if_fail (HDY_IS_HEADER_BAR (self), NULL); return priv->title; } /** * hdy_header_bar_set_subtitle: * @self: a #HdyHeaderBar * @subtitle: (allow-none): a subtitle, or %NULL * * Sets the subtitle of the #HdyHeaderBar. The title should give a user * an additional detail to help him identify the current view. * * Note that HdyHeaderBar by default reserves room for the subtitle, * even if none is currently set. If this is not desired, set the * #HdyHeaderBar:has-subtitle property to %FALSE. * * Since: 0.0.10 */ void hdy_header_bar_set_subtitle (HdyHeaderBar *self, const gchar *subtitle) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); gchar *new_subtitle; g_return_if_fail (HDY_IS_HEADER_BAR (self)); new_subtitle = g_strdup (subtitle); g_free (priv->subtitle); priv->subtitle = new_subtitle; if (priv->subtitle_label != NULL) { gtk_label_set_label (GTK_LABEL (priv->subtitle_label), priv->subtitle); gtk_widget_set_visible (priv->subtitle_label, priv->subtitle && priv->subtitle[0]); gtk_widget_queue_resize (GTK_WIDGET (self)); } gtk_widget_set_visible (priv->subtitle_sizing_label, priv->has_subtitle || (priv->subtitle && priv->subtitle[0])); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUBTITLE]); } /** * hdy_header_bar_get_subtitle: * @self: a #HdyHeaderBar * * Retrieves the subtitle of the header. See hdy_header_bar_set_subtitle(). * * Returns: (nullable): the subtitle of the header, or %NULL if none has * been set explicitly. The returned string is owned by the widget * and must not be modified or freed. * * Since: 0.0.10 */ const gchar * hdy_header_bar_get_subtitle (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); g_return_val_if_fail (HDY_IS_HEADER_BAR (self), NULL); return priv->subtitle; } /** * hdy_header_bar_set_custom_title: * @self: a #HdyHeaderBar * @title_widget: (allow-none): a custom widget to use for a title * * Sets a custom title for the #HdyHeaderBar. * * The title should help a user identify the current view. This * supersedes any title set by hdy_header_bar_set_title() or * hdy_header_bar_set_subtitle(). To achieve the same style as * the builtin title and subtitle, use the “title” and “subtitle” * style classes. * * You should set the custom title to %NULL, for the header title * label to be visible again. * * Since: 0.0.10 */ void hdy_header_bar_set_custom_title (HdyHeaderBar *self, GtkWidget *title_widget) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); g_return_if_fail (HDY_IS_HEADER_BAR (self)); if (title_widget) g_return_if_fail (GTK_IS_WIDGET (title_widget)); /* No need to do anything if the custom widget stays the same */ if (priv->custom_title == title_widget) return; if (priv->custom_title) { GtkWidget *custom = priv->custom_title; priv->custom_title = NULL; gtk_widget_unparent (custom); } if (title_widget != NULL) { priv->custom_title = title_widget; gtk_widget_set_parent (priv->custom_title, GTK_WIDGET (self)); if (priv->label_box != NULL) { GtkWidget *label_box = priv->label_box; priv->label_box = NULL; priv->title_label = NULL; priv->subtitle_label = NULL; gtk_widget_unparent (label_box); } } else { if (priv->label_box == NULL) construct_label_box (self); } gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CUSTOM_TITLE]); } /** * hdy_header_bar_get_custom_title: * @self: a #HdyHeaderBar * * Retrieves the custom title widget of the header. See * hdy_header_bar_set_custom_title(). * * Returns: (nullable) (transfer none): the custom title widget * of the header, or %NULL if none has been set explicitly. * * Since: 0.0.10 */ GtkWidget * hdy_header_bar_get_custom_title (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); g_return_val_if_fail (HDY_IS_HEADER_BAR (self), NULL); return priv->custom_title; } /** * hdy_header_bar_get_show_close_button: * @self: a #HdyHeaderBar * * Returns whether this header bar shows the standard window * decorations. * * Returns: %TRUE if the decorations are shown * * Since: 0.0.10 */ gboolean hdy_header_bar_get_show_close_button (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv; g_return_val_if_fail (HDY_IS_HEADER_BAR (self), FALSE); priv = hdy_header_bar_get_instance_private (self); return priv->shows_wm_decorations; } /** * hdy_header_bar_set_show_close_button: * @self: a #HdyHeaderBar * @setting: %TRUE to show standard window decorations * * Sets whether this header bar shows the standard window decorations, * including close, maximize, and minimize. * * Since: 0.0.10 */ void hdy_header_bar_set_show_close_button (HdyHeaderBar *self, gboolean setting) { HdyHeaderBarPrivate *priv; g_return_if_fail (HDY_IS_HEADER_BAR (self)); priv = hdy_header_bar_get_instance_private (self); setting = setting != FALSE; if (priv->shows_wm_decorations == setting) return; priv->shows_wm_decorations = setting; hdy_header_bar_update_window_buttons (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_CLOSE_BUTTON]); } /** * hdy_header_bar_set_has_subtitle: * @self: a #HdyHeaderBar * @setting: %TRUE to reserve space for a subtitle * * Sets whether the header bar should reserve space * for a subtitle, even if none is currently set. * * Since: 0.0.10 */ void hdy_header_bar_set_has_subtitle (HdyHeaderBar *self, gboolean setting) { HdyHeaderBarPrivate *priv; g_return_if_fail (HDY_IS_HEADER_BAR (self)); priv = hdy_header_bar_get_instance_private (self); setting = setting != FALSE; if (priv->has_subtitle == setting) return; priv->has_subtitle = setting; gtk_widget_set_visible (priv->subtitle_sizing_label, setting || (priv->subtitle && priv->subtitle[0])); gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_HAS_SUBTITLE]); } /** * hdy_header_bar_get_has_subtitle: * @self: a #HdyHeaderBar * * Retrieves whether the header bar reserves space for * a subtitle, regardless if one is currently set or not. * * Returns: %TRUE if the header bar reserves space * for a subtitle * * Since: 0.0.10 */ gboolean hdy_header_bar_get_has_subtitle (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv; g_return_val_if_fail (HDY_IS_HEADER_BAR (self), FALSE); priv = hdy_header_bar_get_instance_private (self); return priv->has_subtitle; } /** * hdy_header_bar_set_decoration_layout: * @self: a #HdyHeaderBar * @layout: (allow-none): a decoration layout, or %NULL to * unset the layout * * Sets the decoration layout for this header bar, overriding * the #GtkSettings:gtk-decoration-layout setting. * * There can be valid reasons for overriding the setting, such * as a header bar design that does not allow for buttons to take * room on the right, or only offers room for a single close button. * Split header bars are another example for overriding the * setting. * * The format of the string is button names, separated by commas. * A colon separates the buttons that should appear on the left * from those on the right. Recognized button names are minimize, * maximize, close, icon (the window icon) and menu (a menu button * for the fallback app menu). * * For example, “menu:minimize,maximize,close” specifies a menu * on the left, and minimize, maximize and close buttons on the right. * * Since: 0.0.10 */ void hdy_header_bar_set_decoration_layout (HdyHeaderBar *self, const gchar *layout) { HdyHeaderBarPrivate *priv; g_return_if_fail (HDY_IS_HEADER_BAR (self)); priv = hdy_header_bar_get_instance_private (self); g_clear_pointer (&priv->decoration_layout, g_free); priv->decoration_layout = g_strdup (layout); priv->decoration_layout_set = (layout != NULL); hdy_header_bar_update_window_buttons (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DECORATION_LAYOUT]); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DECORATION_LAYOUT_SET]); } /** * hdy_header_bar_get_decoration_layout: * @self: a #HdyHeaderBar * * Gets the decoration layout set with * hdy_header_bar_set_decoration_layout(). * * Returns: the decoration layout * * Since: 0.0.10 */ const gchar * hdy_header_bar_get_decoration_layout (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv; g_return_val_if_fail (HDY_IS_HEADER_BAR (self), NULL); priv = hdy_header_bar_get_instance_private (self); return priv->decoration_layout; } /** * hdy_header_bar_get_centering_policy: * @self: a #HdyHeaderBar * * Gets the policy @self follows to horizontally align its center widget. * * Returns: the centering policy * * Since: 0.0.10 */ HdyCenteringPolicy hdy_header_bar_get_centering_policy (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); g_return_val_if_fail (HDY_IS_HEADER_BAR (self), HDY_CENTERING_POLICY_LOOSE); return priv->centering_policy; } /** * hdy_header_bar_set_centering_policy: * @self: a #HdyHeaderBar * @centering_policy: the centering policy * * Sets the policy @self must follow to horizontally align its center widget. * * Since: 0.0.10 */ void hdy_header_bar_set_centering_policy (HdyHeaderBar *self, HdyCenteringPolicy centering_policy) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); g_return_if_fail (HDY_IS_HEADER_BAR (self)); if (priv->centering_policy == centering_policy) return; priv->centering_policy = centering_policy; if (priv->interpolate_size) hdy_header_bar_start_transition (self, priv->transition_duration); else gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CENTERING_POLICY]); } /** * hdy_header_bar_get_transition_duration: * @self: a #HdyHeaderBar * * Returns the amount of time (in milliseconds) that * transitions between pages in @self will take. * * Returns: the transition duration * * Since: 0.0.10 */ guint hdy_header_bar_get_transition_duration (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); g_return_val_if_fail (HDY_IS_HEADER_BAR (self), 0); return priv->transition_duration; } /** * hdy_header_bar_set_transition_duration: * @self: a #HdyHeaderBar * @duration: the new duration, in milliseconds * * Sets the duration that transitions between pages in @self * will take. * * Since: 0.0.10 */ void hdy_header_bar_set_transition_duration (HdyHeaderBar *self, guint duration) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); g_return_if_fail (HDY_IS_HEADER_BAR (self)); if (priv->transition_duration == duration) return; priv->transition_duration = duration; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_DURATION]); } /** * hdy_header_bar_get_transition_running: * @self: a #HdyHeaderBar * * Returns whether the @self is currently in a transition from one page to * another. * * Returns: %TRUE if the transition is currently running, %FALSE otherwise. * * Since: 0.0.10 */ gboolean hdy_header_bar_get_transition_running (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self); g_return_val_if_fail (HDY_IS_HEADER_BAR (self), FALSE); return (priv->tick_id != 0); } /** * hdy_header_bar_get_interpolate_size: * @self: A #HdyHeaderBar * * Gets wether @self should interpolate its size on visible child change. * * See hdy_header_bar_set_interpolate_size(). * * Returns: %TRUE if @self interpolates its size on visible child change, %FALSE if not * * Since: 0.0.10 */ gboolean hdy_header_bar_get_interpolate_size (HdyHeaderBar *self) { HdyHeaderBarPrivate *priv; g_return_val_if_fail (HDY_IS_HEADER_BAR (self), FALSE); priv = hdy_header_bar_get_instance_private (self); return priv->interpolate_size; } /** * hdy_header_bar_set_interpolate_size: * @self: A #HdyHeaderBar * @interpolate_size: %TRUE to interpolate the size * * Sets whether or not @self will interpolate the size of its opposing * orientation when changing the visible child. If %TRUE, @self will interpolate * its size between the one of the previous visible child and the one of the new * visible child, according to the set transition duration and the orientation, * e.g. if @self is horizontal, it will interpolate the its height. * * Since: 0.0.10 */ void hdy_header_bar_set_interpolate_size (HdyHeaderBar *self, gboolean interpolate_size) { HdyHeaderBarPrivate *priv; g_return_if_fail (HDY_IS_HEADER_BAR (self)); priv = hdy_header_bar_get_instance_private (self); interpolate_size = !!interpolate_size; if (priv->interpolate_size == interpolate_size) return; priv->interpolate_size = interpolate_size; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INTERPOLATE_SIZE]); } libhandy-0.0.13/src/hdy-header-bar.h000066400000000000000000000071751360136463700171240ustar00rootroot00000000000000/* * Copyright (c) 2013 Red Hat, Inc. * Copyright (C) 2019 Purism SPC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_HEADER_BAR (hdy_header_bar_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyHeaderBar, hdy_header_bar, HDY, HEADER_BAR, GtkContainer) typedef enum { HDY_CENTERING_POLICY_LOOSE, HDY_CENTERING_POLICY_STRICT, } HdyCenteringPolicy; /** * HdyHeaderBarClass * @parent_class: The parent class */ struct _HdyHeaderBarClass { GtkContainerClass parent_class; }; GtkWidget *hdy_header_bar_new (void); const gchar *hdy_header_bar_get_title (HdyHeaderBar *self); void hdy_header_bar_set_title (HdyHeaderBar *self, const gchar *title); const gchar *hdy_header_bar_get_subtitle (HdyHeaderBar *self); void hdy_header_bar_set_subtitle (HdyHeaderBar *self, const gchar *subtitle); GtkWidget *hdy_header_bar_get_custom_title (HdyHeaderBar *self); void hdy_header_bar_set_custom_title (HdyHeaderBar *self, GtkWidget *title_widget); void hdy_header_bar_pack_start (HdyHeaderBar *self, GtkWidget *child); void hdy_header_bar_pack_end (HdyHeaderBar *self, GtkWidget *child); gboolean hdy_header_bar_get_show_close_button (HdyHeaderBar *self); void hdy_header_bar_set_show_close_button (HdyHeaderBar *self, gboolean setting); gboolean hdy_header_bar_get_has_subtitle (HdyHeaderBar *self); void hdy_header_bar_set_has_subtitle (HdyHeaderBar *self, gboolean setting); const gchar *hdy_header_bar_get_decoration_layout (HdyHeaderBar *self); void hdy_header_bar_set_decoration_layout (HdyHeaderBar *self, const gchar *layout); HdyCenteringPolicy hdy_header_bar_get_centering_policy (HdyHeaderBar *self); void hdy_header_bar_set_centering_policy (HdyHeaderBar *self, HdyCenteringPolicy centering_policy); guint hdy_header_bar_get_transition_duration (HdyHeaderBar *self); void hdy_header_bar_set_transition_duration (HdyHeaderBar *self, guint duration); gboolean hdy_header_bar_get_transition_running (HdyHeaderBar *self); gboolean hdy_header_bar_get_interpolate_size (HdyHeaderBar *self); void hdy_header_bar_set_interpolate_size (HdyHeaderBar *self, gboolean interpolate_size); G_END_DECLS libhandy-0.0.13/src/hdy-header-group.c000066400000000000000000000446371360136463700175130ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-header-group.h" typedef struct { GSList *header_bars; GtkHeaderBar *focus; } HdyHeaderGroupPrivate; static void hdy_header_group_buildable_init (GtkBuildableIface *iface); static gboolean hdy_header_group_buildable_custom_tag_start (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, GMarkupParser *parser, gpointer *data); static void hdy_header_group_buildable_custom_finished (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, gpointer user_data); G_DEFINE_TYPE_WITH_CODE (HdyHeaderGroup, hdy_header_group, G_TYPE_OBJECT, G_ADD_PRIVATE (HdyHeaderGroup) G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, hdy_header_group_buildable_init)) enum { PROP_0, PROP_FOCUS, N_PROPS }; static GParamSpec *props [N_PROPS]; static gboolean contains (HdyHeaderGroup *self, GtkHeaderBar *header_bar) { HdyHeaderGroupPrivate *priv; GSList *header_bars; priv = hdy_header_group_get_instance_private (self); for (header_bars = priv->header_bars; header_bars != NULL; header_bars = header_bars->next) if (header_bars->data == header_bar) return TRUE; return FALSE; } static void update_decoration_layouts (HdyHeaderGroup *self) { HdyHeaderGroupPrivate *priv; GSList *header_bars; GtkSettings *settings; GtkHeaderBar *start_headerbar = NULL, *end_headerbar = NULL; g_autofree gchar *layout = NULL; g_autofree gchar *start_layout = NULL; g_autofree gchar *end_layout = NULL; g_auto(GStrv) ends = NULL; g_return_if_fail (HDY_IS_HEADER_GROUP (self)); priv = hdy_header_group_get_instance_private (self); header_bars = priv->header_bars; if (header_bars == NULL) return; settings = gtk_settings_get_default (); g_object_get (G_OBJECT (settings), "gtk-decoration-layout", &layout, NULL); if (layout == NULL) layout = g_strdup (":"); if (priv->focus != NULL) { for (; header_bars != NULL; header_bars = header_bars->next) if (header_bars->data == priv->focus && gtk_widget_get_mapped (header_bars->data)) gtk_header_bar_set_decoration_layout (header_bars->data, layout); else gtk_header_bar_set_decoration_layout (header_bars->data, ":"); return; } for (; header_bars != NULL; header_bars = header_bars->next) { gtk_header_bar_set_decoration_layout (header_bars->data, ":"); if (!gtk_widget_get_mapped (header_bars->data)) continue; /* The headerbars are in reverse order in the list. */ start_headerbar = header_bars->data; if (end_headerbar == NULL) end_headerbar = header_bars->data; } if (start_headerbar == NULL || end_headerbar == NULL) return; if (start_headerbar == end_headerbar) { gtk_header_bar_set_decoration_layout (start_headerbar, layout); return; } ends = g_strsplit (layout, ":", 2); if (g_strv_length (ends) >= 2) { start_layout = g_strdup_printf ("%s:", ends[0]); end_layout = g_strdup_printf (":%s", ends[1]); } else { start_layout = g_strdup (":"); end_layout = g_strdup (":"); } gtk_header_bar_set_decoration_layout (start_headerbar, start_layout); gtk_header_bar_set_decoration_layout (end_headerbar, end_layout); } static void header_bar_destroyed (HdyHeaderGroup *self, GtkHeaderBar *header_bar) { HdyHeaderGroupPrivate *priv; g_return_if_fail (HDY_IS_HEADER_GROUP (self)); priv = hdy_header_group_get_instance_private (self); priv->header_bars = g_slist_remove (priv->header_bars, header_bar); g_object_unref (self); } HdyHeaderGroup * hdy_header_group_new (void) { return g_object_new (HDY_TYPE_HEADER_GROUP, NULL); } /** * hdy_header_group_add_header_bar: * @self: a #HdyHeaderGroup * @header_bar: the #GtkHeaderBar to add * * Adds a header bar to a #HdyHeaderGroup. The decoration layout of the * widgets will be edited depending on their position in the composite header * bar, the start widget displaying only the start of the user's decoration * layout and the end widget displaying only its end while widgets in the middle * won't display anything. A header bar can be set as having the focus to * display all the decorations. See gtk_header_bar_set_decoration_layout(). * * When the widget is destroyed or no longer referenced elsewhere, it will * be removed from the header group. */ void hdy_header_group_add_header_bar (HdyHeaderGroup *self, GtkHeaderBar *header_bar) { HdyHeaderGroupPrivate *priv; g_return_if_fail (HDY_IS_HEADER_GROUP (self)); g_return_if_fail (GTK_IS_HEADER_BAR (header_bar)); priv = hdy_header_group_get_instance_private (self); g_signal_connect_swapped (header_bar, "map", G_CALLBACK (update_decoration_layouts), self); g_signal_connect_swapped (header_bar, "unmap", G_CALLBACK (update_decoration_layouts), self); priv->header_bars = g_slist_prepend (priv->header_bars, header_bar); g_object_ref (self); g_signal_connect_swapped (header_bar, "destroy", G_CALLBACK (header_bar_destroyed), self); update_decoration_layouts (self); } /** * hdy_header_group_remove_header_bar: * @self: a #HdyHeaderGroup * @header_bar: the #GtkHeaderBar to remove * * Removes a widget from a #HdyHeaderGroup **/ void hdy_header_group_remove_header_bar (HdyHeaderGroup *self, GtkHeaderBar *header_bar) { HdyHeaderGroupPrivate *priv; g_return_if_fail (HDY_IS_HEADER_GROUP (self)); g_return_if_fail (GTK_IS_HEADER_BAR (header_bar)); g_return_if_fail (contains (self, header_bar)); priv = hdy_header_group_get_instance_private (self); priv->header_bars = g_slist_remove (priv->header_bars, header_bar); if (priv->focus == header_bar) hdy_header_group_set_focus (self, NULL); g_signal_handlers_disconnect_by_data (header_bar, self); g_object_unref (self); } /** * hdy_header_group_get_header_bars: * @self: a #HdyHeaderGroup * * Returns the list of headerbars associated with @self. * * Returns: (element-type GtkHeaderBar) (transfer none): a #GSList of * headerbars. The list is owned by libhandy and should not be modified. **/ GSList * hdy_header_group_get_header_bars (HdyHeaderGroup *self) { HdyHeaderGroupPrivate *priv; g_return_val_if_fail (HDY_IS_HEADER_GROUP (self), NULL); priv = hdy_header_group_get_instance_private (self); return priv->header_bars; } /** * hdy_header_group_set_focus: * @self: a #HdyHeaderGroup * @header_bar: (nullable): a #GtkHeaderBar of @self, or %NULL * * Sets the the currently focused header bar. If @header_bar is %NULL, the * decoration will be spread as if the header bars of the group were only one, * otherwise @header_bar will be the only one to receive the decoration. */ void hdy_header_group_set_focus (HdyHeaderGroup *self, GtkHeaderBar *header_bar) { HdyHeaderGroupPrivate *priv; g_return_if_fail (HDY_IS_HEADER_GROUP (self)); g_return_if_fail (header_bar == NULL || GTK_IS_HEADER_BAR (header_bar)); g_return_if_fail (header_bar == NULL || contains (self, header_bar)); priv = hdy_header_group_get_instance_private (self); priv->focus = header_bar; update_decoration_layouts (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FOCUS]); } /** * hdy_header_group_get_focus: * @self: a #HdyHeaderGroup * * Returns: (nullable) (transfer none): The currently focused header bar */ GtkHeaderBar * hdy_header_group_get_focus (HdyHeaderGroup *self) { HdyHeaderGroupPrivate *priv; g_return_val_if_fail (HDY_IS_HEADER_GROUP (self), FALSE); priv = hdy_header_group_get_instance_private (self); return priv->focus; } typedef struct { gchar *name; gint line; gint col; } ItemData; static void item_data_free (gpointer data) { ItemData *item_data = data; g_free (item_data->name); g_free (item_data); } typedef struct { GObject *object; GtkBuilder *builder; GSList *items; } GSListSubParserData; static void hdy_header_group_dispose (GObject *object) { HdyHeaderGroup *self = (HdyHeaderGroup *)object; HdyHeaderGroupPrivate *priv = hdy_header_group_get_instance_private (self); g_slist_free_full (priv->header_bars, (GDestroyNotify) g_object_unref); priv->header_bars = NULL; priv->focus = NULL; G_OBJECT_CLASS (hdy_header_group_parent_class)->dispose (object); } static void hdy_header_group_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyHeaderGroup *self = HDY_HEADER_GROUP (object); switch (prop_id) { case PROP_FOCUS: g_value_set_object (value, hdy_header_group_get_focus (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_header_group_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyHeaderGroup *self = HDY_HEADER_GROUP (object); switch (prop_id) { case PROP_FOCUS: hdy_header_group_set_focus (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } /*< private > * @builder: a #GtkBuilder * @context: the #GMarkupParseContext * @parent_name: the name of the expected parent element * @error: return location for an error * * Checks that the parent element of the currently handled * start tag is @parent_name and set @error if it isn't. * * This is intended to be called in start_element vfuncs to * ensure that element nesting is as intended. * * Returns: %TRUE if @parent_name is the parent element */ /* This has been copied and modified from gtkbuilder.c. */ static gboolean _gtk_builder_check_parent (GtkBuilder *builder, GMarkupParseContext *context, const gchar *parent_name, GError **error) { const GSList *stack; gint line, col; const gchar *parent; const gchar *element; stack = g_markup_parse_context_get_element_stack (context); element = (const gchar *)stack->data; parent = stack->next ? (const gchar *)stack->next->data : ""; if (g_str_equal (parent_name, parent) || (g_str_equal (parent_name, "object") && g_str_equal (parent, "template"))) return TRUE; g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, GTK_BUILDER_ERROR, GTK_BUILDER_ERROR_INVALID_TAG, ".:%d:%d Can't use <%s> here", line, col, element); return FALSE; } /*< private > * _gtk_builder_prefix_error: * @builder: a #GtkBuilder * @context: the #GMarkupParseContext * @error: an error * * Calls g_prefix_error() to prepend a filename:line:column marker * to the given error. The filename is taken from @builder, and * the line and column are obtained by calling * g_markup_parse_context_get_position(). * * This is intended to be called on errors returned by * g_markup_collect_attributes() in a start_element vfunc. */ /* This has been copied and modified from gtkbuilder.c. */ static void _gtk_builder_prefix_error (GtkBuilder *builder, GMarkupParseContext *context, GError **error) { gint line, col; g_markup_parse_context_get_position (context, &line, &col); g_prefix_error (error, ".:%d:%d ", line, col); } /*< private > * _gtk_builder_error_unhandled_tag: * @builder: a #GtkBuilder * @context: the #GMarkupParseContext * @object: name of the object that is being handled * @element_name: name of the element whose start tag is being handled * @error: return location for the error * * Sets @error to a suitable error indicating that an @element_name * tag is not expected in the custom markup for @object. * * This is intended to be called in a start_element vfunc. */ /* This has been copied and modified from gtkbuilder.c. */ static void _gtk_builder_error_unhandled_tag (GtkBuilder *builder, GMarkupParseContext *context, const gchar *object, const gchar *element_name, GError **error) { gint line, col; g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, GTK_BUILDER_ERROR, GTK_BUILDER_ERROR_UNHANDLED_TAG, ".:%d:%d Unsupported tag for %s: <%s>", line, col, object, element_name); } /* This has been copied and modified from gtksizegroup.c. */ static void header_group_start_element (GMarkupParseContext *context, const gchar *element_name, const gchar **names, const gchar **values, gpointer user_data, GError **error) { GSListSubParserData *data = (GSListSubParserData*)user_data; if (strcmp (element_name, "headerbar") == 0) { const gchar *name; ItemData *item_data; if (!_gtk_builder_check_parent (data->builder, context, "headerbars", error)) return; if (!g_markup_collect_attributes (element_name, names, values, error, G_MARKUP_COLLECT_STRING, "name", &name, G_MARKUP_COLLECT_INVALID)) { _gtk_builder_prefix_error (data->builder, context, error); return; } item_data = g_new (ItemData, 1); item_data->name = g_strdup (name); g_markup_parse_context_get_position (context, &item_data->line, &item_data->col); data->items = g_slist_prepend (data->items, item_data); } else if (strcmp (element_name, "headerbars") == 0) { if (!_gtk_builder_check_parent (data->builder, context, "object", error)) return; if (!g_markup_collect_attributes (element_name, names, values, error, G_MARKUP_COLLECT_INVALID, NULL, NULL, G_MARKUP_COLLECT_INVALID)) _gtk_builder_prefix_error (data->builder, context, error); } else { _gtk_builder_error_unhandled_tag (data->builder, context, "HdyHeaderGroup", element_name, error); } } /* This has been copied and modified from gtksizegroup.c. */ static const GMarkupParser header_group_parser = { header_group_start_element }; /* This has been copied and modified from gtksizegroup.c. */ static gboolean hdy_header_group_buildable_custom_tag_start (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, GMarkupParser *parser, gpointer *parser_data) { GSListSubParserData *data; if (child) return FALSE; if (strcmp (tagname, "headerbars") == 0) { data = g_slice_new0 (GSListSubParserData); data->items = NULL; data->object = G_OBJECT (buildable); data->builder = builder; *parser = header_group_parser; *parser_data = data; return TRUE; } return FALSE; } /* This has been copied and modified from gtksizegroup.c. */ static void hdy_header_group_buildable_custom_finished (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, gpointer user_data) { GSList *l; GSListSubParserData *data; GObject *object; if (strcmp (tagname, "headerbars") != 0) return; data = (GSListSubParserData*)user_data; data->items = g_slist_reverse (data->items); for (l = data->items; l; l = l->next) { ItemData *item_data = l->data; object = gtk_builder_get_object (builder, item_data->name); if (!object) continue; hdy_header_group_add_header_bar (HDY_HEADER_GROUP (data->object), GTK_HEADER_BAR (object)); } g_slist_free_full (data->items, item_data_free); g_slice_free (GSListSubParserData, data); } static void hdy_header_group_class_init (HdyHeaderGroupClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = hdy_header_group_dispose; object_class->get_property = hdy_header_group_get_property; object_class->set_property = hdy_header_group_set_property; /** * HdyHeaderGroup:focus: * * The the currently focused header bar. If %NULL, the decoration will be * spread as if the header bars of the group were only one, otherwise the * focused header bar will be the only one to receive the decoration. */ props[PROP_FOCUS] = g_param_spec_object ("focus", _("Focus"), _("The header bar that should have the focus"), GTK_TYPE_HEADER_BAR, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, N_PROPS, props); } static void hdy_header_group_init (HdyHeaderGroup *self) { GtkSettings *settings = gtk_settings_get_default (); g_signal_connect_swapped (settings, "notify::gtk-decoration-layout", G_CALLBACK (update_decoration_layouts), self); } static void hdy_header_group_buildable_init (GtkBuildableIface *iface) { iface->custom_tag_start = hdy_header_group_buildable_custom_tag_start; iface->custom_finished = hdy_header_group_buildable_custom_finished; } libhandy-0.0.13/src/hdy-header-group.h000066400000000000000000000022151360136463700175020ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_HEADER_GROUP (hdy_header_group_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyHeaderGroup, hdy_header_group, HDY, HEADER_GROUP, GObject) /** * HdyHeaderGroupClass * @parent_class: The parent class */ struct _HdyHeaderGroupClass { GObjectClass parent_class; }; HdyHeaderGroup *hdy_header_group_new (void); void hdy_header_group_add_header_bar (HdyHeaderGroup *self, GtkHeaderBar *header_bar); GtkHeaderBar *hdy_header_group_get_focus (HdyHeaderGroup *self); void hdy_header_group_set_focus (HdyHeaderGroup *self, GtkHeaderBar *header_bar); GSList * hdy_header_group_get_header_bars (HdyHeaderGroup *self); void hdy_header_group_remove_header_bar (HdyHeaderGroup *self, GtkHeaderBar *header_bar); G_END_DECLS libhandy-0.0.13/src/hdy-keypad-button-private.h000066400000000000000000000015611360136463700213610ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_KEYPAD_BUTTON (hdy_keypad_button_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyKeypadButton, hdy_keypad_button, HDY, KEYPAD_BUTTON, GtkButton) struct _HdyKeypadButtonClass { GtkButtonClass parent_class; }; GtkWidget *hdy_keypad_button_new (const gchar *symbols); gchar hdy_keypad_button_get_digit (HdyKeypadButton *self); const gchar *hdy_keypad_button_get_symbols (HdyKeypadButton *self); void hdy_keypad_button_show_symbols (HdyKeypadButton *self, gboolean visible); G_END_DECLS libhandy-0.0.13/src/hdy-keypad-button.c000066400000000000000000000262521360136463700177100ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-style-private.h" #include "hdy-keypad-button-private.h" /** * PRIVATE:hdy-keypad-button * @short_description: A button on a #HdyKeypad keypad * @Title: HdyKeypadButton * * The #HdyKeypadButton widget is a single button on an #HdyKeypad. It * can represent a single symbol (typically a digit) plus an arbitrary * number of symbols that are displayed below it. */ enum { PROP_0, PROP_DIGIT, PROP_SYMBOLS, PROP_SHOW_SYMBOLS, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; typedef struct { GtkLabel *label, *secondary_label; gchar *symbols; } HdyKeypadButtonPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyKeypadButton, hdy_keypad_button, GTK_TYPE_BUTTON) static void format_label(HdyKeypadButton *self) { HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self); g_autofree gchar *text = NULL; gchar *secondary_text = NULL; if (priv->symbols != NULL && *(priv->symbols) != '\0') { secondary_text = g_utf8_find_next_char (priv->symbols, NULL); text = g_strndup (priv->symbols, 1); } gtk_label_set_label (priv->label, text); gtk_label_set_label (priv->secondary_label, secondary_text); } static void hdy_keypad_button_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { HdyKeypadButton *self = HDY_KEYPAD_BUTTON (object); HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self); switch (property_id) { case PROP_SYMBOLS: if (g_strcmp0 (priv->symbols, g_value_get_string (value)) != 0) { g_free (priv->symbols); priv->symbols = g_value_dup_string (value); format_label(self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SYMBOLS]); } break; case PROP_SHOW_SYMBOLS: hdy_keypad_button_show_symbols (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_keypad_button_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { HdyKeypadButton *self = HDY_KEYPAD_BUTTON (object); HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self); switch (property_id) { case PROP_DIGIT: g_value_set_schar (value, hdy_keypad_button_get_digit (self)); break; case PROP_SYMBOLS: g_value_set_string (value, hdy_keypad_button_get_symbols (self)); break; case PROP_SHOW_SYMBOLS: g_value_set_boolean (value, gtk_widget_is_visible (GTK_WIDGET (priv->secondary_label))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /* This private method is prefixed by the call name because it will be a virtual * method in GTK 4. */ static void hdy_keypad_button_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (hdy_keypad_button_parent_class); gint min1, min2, nat1, nat2; if (for_size < 0) { widget_class->get_preferred_width (widget, &min1, &nat1); widget_class->get_preferred_height (widget, &min2, &nat2); } else { if (orientation == GTK_ORIENTATION_HORIZONTAL) widget_class->get_preferred_width_for_height (widget, for_size, &min1, &nat1); else widget_class->get_preferred_height_for_width (widget, for_size, &min1, &nat1); min2 = nat2 = for_size; } if (minimum) *minimum = MAX (min1, min2); if (natural) *natural = MAX (nat1, nat2); } static GtkSizeRequestMode hdy_keypad_button_get_request_mode (GtkWidget *widget) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (hdy_keypad_button_parent_class); gint min1, min2; widget_class->get_preferred_width (widget, &min1, NULL); widget_class->get_preferred_height (widget, &min2, NULL); if (min1 < min2) return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; else return GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT; } static void hdy_keypad_button_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width) { hdy_keypad_button_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, minimum_width, natural_width, NULL, NULL); } static void hdy_keypad_button_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height) { hdy_keypad_button_measure (widget, GTK_ORIENTATION_VERTICAL, -1, minimum_height, natural_height, NULL, NULL); } static void hdy_keypad_button_get_preferred_width_for_height (GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width) { *minimum_width = height; *natural_width = height; } static void hdy_keypad_button_get_preferred_height_for_width (GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height) { *minimum_height = width; *natural_height = width; } static void hdy_keypad_button_finalize (GObject *object) { HdyKeypadButton *self = HDY_KEYPAD_BUTTON (object); HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self); g_clear_pointer (&priv->symbols, g_free); G_OBJECT_CLASS (hdy_keypad_button_parent_class)->finalize (object); } static void hdy_keypad_button_class_init (HdyKeypadButtonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->set_property = hdy_keypad_button_set_property; object_class->get_property = hdy_keypad_button_get_property; object_class->finalize = hdy_keypad_button_finalize; widget_class->get_request_mode = hdy_keypad_button_get_request_mode; widget_class->get_preferred_width = hdy_keypad_button_get_preferred_width; widget_class->get_preferred_height = hdy_keypad_button_get_preferred_height; widget_class->get_preferred_width_for_height = hdy_keypad_button_get_preferred_width_for_height; widget_class->get_preferred_height_for_width = hdy_keypad_button_get_preferred_height_for_width; props[PROP_DIGIT] = g_param_spec_int ("digit", _("Digit"), _("The keypad digit of the button"), -1, INT_MAX, 0, G_PARAM_READABLE); props[PROP_SYMBOLS] = g_param_spec_string ("symbols", _("Symbols"), _("The keypad symbols of the button. The first symbol is used as the digit"), "", G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_SHOW_SYMBOLS] = g_param_spec_boolean ("show_symbols", _("Show Symbols"), _("Whether the second line of symbols should be shown or not"), TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-keypad-button.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyKeypadButton, label); gtk_widget_class_bind_template_child_private (widget_class, HdyKeypadButton, secondary_label); } static void hdy_keypad_button_init (HdyKeypadButton *self) { HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self); g_autoptr (GtkCssProvider) provider_digit = NULL; g_autoptr (GtkCssProvider) provider_letters = NULL; gtk_widget_init_template (GTK_WIDGET (self)); provider_digit = gtk_css_provider_new (); gtk_css_provider_load_from_resource (provider_digit, "/sm/puri/handy/style/hdy-keypad-digit.css"); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->label)), GTK_STYLE_PROVIDER (provider_digit), HDY_STYLE_PROVIDER_PRIORITY); provider_letters = gtk_css_provider_new (); gtk_css_provider_load_from_resource (provider_letters, "/sm/puri/handy/style/hdy-keypad-letters.css"); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->secondary_label)), GTK_STYLE_PROVIDER (provider_letters), HDY_STYLE_PROVIDER_PRIORITY); priv->symbols = NULL; } /** * hdy_keypad_button_new: * @symbols: (nullable): the symbols displayed on the #HdyKeypadButton * * Create a new #HdyKeypadButton which displays @symbols, * where the first char is used as the main and the other symbols are shown below * * Returns: the newly created #HdyKeypadButton widget */ GtkWidget * hdy_keypad_button_new (const gchar *symbols) { return g_object_new (HDY_TYPE_KEYPAD_BUTTON, "symbols", symbols, NULL); } /** * hdy_keypad_button_get_digit: * @self: a #HdyKeypadButton * * Get the #HdyKeypadButton's digit. * * Returns: the button's digit */ char hdy_keypad_button_get_digit (HdyKeypadButton *self) { HdyKeypadButtonPrivate *priv; g_return_val_if_fail (HDY_IS_KEYPAD_BUTTON (self), '\0'); priv = hdy_keypad_button_get_instance_private(self); if (priv->symbols == NULL) return ('\0'); return *(priv->symbols); } /** * hdy_keypad_button_get_symbols: * @self: a #HdyKeypadButton * * Get the #HdyKeypadButton's symbols. * * Returns: the button's symbols including the digit. */ const char* hdy_keypad_button_get_symbols (HdyKeypadButton *self) { HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self); g_return_val_if_fail (HDY_IS_KEYPAD_BUTTON (self), NULL); return priv->symbols; } /** * hdy_keypad_button_show_symbols: * @self: a #HdyKeypadButton * @visible: whether the second line should be shown or not * * Sets the visibility of the second line of symbols for #HdyKeypadButton * */ void hdy_keypad_button_show_symbols (HdyKeypadButton *self, gboolean visible) { HdyKeypadButtonPrivate *priv; gboolean old_visible; g_return_if_fail (HDY_IS_KEYPAD_BUTTON (self)); priv = hdy_keypad_button_get_instance_private(self); old_visible = gtk_widget_get_visible (GTK_WIDGET (priv->secondary_label)); if (old_visible != visible) { gtk_widget_set_visible (GTK_WIDGET (priv->secondary_label), visible); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_SYMBOLS]); } } libhandy-0.0.13/src/hdy-keypad-button.ui000066400000000000000000000022511360136463700200740ustar00rootroot00000000000000 libhandy-0.0.13/src/hdy-keypad-digit.css000066400000000000000000000000751360136463700200360ustar00rootroot00000000000000hdykeypad .digit { font-size: 200%; font-weight: bold; } libhandy-0.0.13/src/hdy-keypad-letters.css000066400000000000000000000000511360136463700204120ustar00rootroot00000000000000hdykeypad .letters { font-size: 70%; } libhandy-0.0.13/src/hdy-keypad-symbol.css000066400000000000000000000000511360136463700202350ustar00rootroot00000000000000hdykeypad .symbol { font-size: 160%; } libhandy-0.0.13/src/hdy-keypad.c000066400000000000000000000322251360136463700163740ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-style-private.h" #include "hdy-keypad.h" #include "hdy-keypad-button-private.h" /** * SECTION:hdy-keypad * @short_description: A keypad for dialing numbers * @Title: HdyKeypad * * The #HdyKeypad widget is a keypad for entering numbers such as phone numbers * or PIN codes. * * This widget should not be altered using the #GtkGrid and #GtkContainer APIs, * they are considered internal to this widget, using them externally will lead to unexpected results. */ typedef struct { GtkWidget *entry; GtkWidget *label_asterisk; GtkWidget *label_hash; GtkGesture *long_press_zero_gesture; gboolean only_digits; gboolean show_symbols; } HdyKeypadPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyKeypad, hdy_keypad, GTK_TYPE_GRID) enum { PROP_0, PROP_SHOW_SYMBOLS, PROP_ONLY_DIGITS, PROP_ENTRY, PROP_RIGHT_ACTION, PROP_LEFT_ACTION, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; static void symbol_clicked (HdyKeypad *self, gchar symbol) { HdyKeypadPrivate *priv; g_autofree gchar *string = g_strdup_printf ("%c", symbol); g_return_if_fail (HDY_IS_KEYPAD (self)); priv = hdy_keypad_get_instance_private (self); g_return_if_fail (priv->entry != NULL); g_signal_emit_by_name(GTK_ENTRY (priv->entry), "insert-at-cursor", string, NULL); /* Set focus to the entry only when it can get focus * https://gitlab.gnome.org/GNOME/gtk/issues/2204 */ if (gtk_widget_get_can_focus (priv->entry)) { gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->entry)); } } static void button_clicked_cb (HdyKeypad *self, HdyKeypadButton *btn) { gchar digit; g_return_if_fail (HDY_IS_KEYPAD (self)); g_return_if_fail (HDY_IS_KEYPAD_BUTTON (btn)); digit = hdy_keypad_button_get_digit (btn); symbol_clicked (self, digit); g_debug ("Button with number %c was pressed", digit); } static void asterisk_button_clicked_cb (HdyKeypad *self, GtkWidget *btn) { g_return_if_fail (HDY_IS_KEYPAD (self)); symbol_clicked (self, '*'); g_debug ("Button with * was pressed"); } static void hash_button_clicked_cb (HdyKeypad *self, GtkWidget *btn) { g_return_if_fail (HDY_IS_KEYPAD (self)); symbol_clicked (self, '#'); g_debug ("Button with # was pressed"); } static void insert_text_cb (HdyKeypad *self, gchar *text, gint length, gpointer position, GtkEditable *editable) { HdyKeypadPrivate *priv; g_return_if_fail (HDY_IS_KEYPAD (self)); priv = hdy_keypad_get_instance_private (self); g_return_if_fail (length == 1); if (g_ascii_isdigit (*text)) return; if (!priv->only_digits && strchr("#*+", *text)) return; g_signal_stop_emission_by_name (editable, "insert-text"); } static void long_press_zero_cb (HdyKeypad *self, gdouble x, gdouble y, GtkGesture *gesture) { HdyKeypadPrivate *priv; g_return_if_fail (HDY_IS_KEYPAD (self)); priv = hdy_keypad_get_instance_private (self); if (priv->only_digits) return; g_debug ("Long press on zero button"); symbol_clicked (self, '+'); gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); } static void hdy_keypad_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { HdyKeypad *self = HDY_KEYPAD (object); HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (self); switch (property_id) { case PROP_SHOW_SYMBOLS: hdy_keypad_show_symbols (self, g_value_get_boolean (value)); break; case PROP_ONLY_DIGITS: if (g_value_get_boolean (value) != priv->only_digits) { priv->only_digits = g_value_get_boolean (value); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ONLY_DIGITS]); } break; case PROP_ENTRY: hdy_keypad_set_entry (self, g_value_get_object (value)); break; case PROP_RIGHT_ACTION: hdy_keypad_set_right_action (self, g_value_get_object (value)); break; case PROP_LEFT_ACTION: hdy_keypad_set_left_action (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_keypad_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { HdyKeypad *self = HDY_KEYPAD (object); HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (self); switch (property_id) { case PROP_SHOW_SYMBOLS: g_value_set_boolean (value, priv->show_symbols); break; case PROP_ONLY_DIGITS: g_value_set_boolean (value, priv->only_digits); break; case PROP_ENTRY: g_value_set_object (value, priv->entry); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_keypad_constructed (GObject *object) { HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (HDY_KEYPAD (object)); g_autoptr (GtkCssProvider) provider = NULL; G_OBJECT_CLASS (hdy_keypad_parent_class)->constructed (object); provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (provider, "/sm/puri/handy/style/hdy-keypad-symbol.css"); gtk_style_context_add_provider (gtk_widget_get_style_context (priv->label_asterisk), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (priv->label_hash), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); } static void hdy_keypad_finalize (GObject *object) { HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (HDY_KEYPAD (object)); if (priv->long_press_zero_gesture != NULL) g_object_unref (priv->long_press_zero_gesture); G_OBJECT_CLASS (hdy_keypad_parent_class)->finalize (object); } static void hdy_keypad_class_init (HdyKeypadClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = hdy_keypad_finalize; object_class->constructed = hdy_keypad_constructed; object_class->set_property = hdy_keypad_set_property; object_class->get_property = hdy_keypad_get_property; props[PROP_SHOW_SYMBOLS] = g_param_spec_boolean ("show-symbols", _("Show Symbols"), _("Whether the second line of symbols should be shown or not"), TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_ONLY_DIGITS] = g_param_spec_boolean ("only-digits", _("Only Digits"), _("Whether the keypad should show only digits or also extra buttons for #, *"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_ENTRY] = g_param_spec_object ("entry", _("Entry widget"), _("The entry widget connected to the keypad"), GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_RIGHT_ACTION] = g_param_spec_object ("right-action", _("Right action widget"), _("The right action widget"), GTK_TYPE_WIDGET, G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_LEFT_ACTION] = g_param_spec_object ("left-action", _("Left action widget"), _("The left action widget"), GTK_TYPE_WIDGET, G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-keypad.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyKeypad, label_asterisk); gtk_widget_class_bind_template_child_private (widget_class, HdyKeypad, label_hash); gtk_widget_class_bind_template_child_private (widget_class, HdyKeypad, long_press_zero_gesture); gtk_widget_class_bind_template_callback(widget_class, button_clicked_cb); gtk_widget_class_bind_template_callback(widget_class, asterisk_button_clicked_cb); gtk_widget_class_bind_template_callback(widget_class, hash_button_clicked_cb); gtk_widget_class_bind_template_callback(widget_class, long_press_zero_cb); gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_DIAL); gtk_widget_class_set_css_name (widget_class, "hdykeypad"); } static void hdy_keypad_init (HdyKeypad *self) { gtk_widget_init_template (GTK_WIDGET (self)); } /** * hdy_keypad_new: * @only_digits: whether the keypad should show only digits or also extra buttons for #, * * @show_symbols: whether the keypad should show the second line or only the main digit * * Create a new #HdyKeypad widget. * * Returns: the newly created #HdyKeypad widget * */ GtkWidget *hdy_keypad_new (gboolean only_digits, gboolean show_symbols) { return g_object_new (HDY_TYPE_KEYPAD, "only-digits", only_digits, "show-symbols", show_symbols, NULL); } /** * hdy_keypad_show_symbols: * @self: a #HdyKeypad * @visible: whether the second line on buttons should be shown or not * * Sets the visibility of symbols (excluding the main digit) on each button in the #HdyKeypad * */ void hdy_keypad_show_symbols (HdyKeypad *self, gboolean visible) { HdyKeypadPrivate *priv = hdy_keypad_get_instance_private(self); g_return_if_fail (HDY_IS_KEYPAD (self)); if (visible == priv->show_symbols) return; priv->show_symbols = visible; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_SYMBOLS]); } /** * hdy_keypad_set_entry: * @self: a #HdyKeypad * @entry: a #GtkEntry * * Binds a #GtkEntry to the keypad and it blocks every * input which wouldn't be possible to type with with the keypad * */ void hdy_keypad_set_entry (HdyKeypad *self, GtkEntry *entry) { HdyKeypadPrivate *priv; g_return_if_fail (HDY_IS_KEYPAD (self)); g_return_if_fail (GTK_IS_ENTRY (entry)); priv = hdy_keypad_get_instance_private(self); if (priv->entry != NULL) { g_object_unref (priv->entry); } if (entry == NULL) { priv->entry = NULL; return; } priv->entry = GTK_WIDGET (g_object_ref (entry)); gtk_widget_show (priv->entry); /* Workaround: To keep the osk closed * https://gitlab.gnome.org/GNOME/gtk/merge_requests/978#note_546576 */ g_object_set (priv->entry, "im-module", "gtk-im-context-none", NULL); g_signal_connect_swapped (G_OBJECT (priv->entry), "insert-text", G_CALLBACK (insert_text_cb), self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ENTRY]); } /** * hdy_keypad_get_entry: * @self: a #HdyKeypad * * Get the connected entry. See hdy_keypad_set_entry () for details * * Returns: (transfer none): the set #GtkEntry or NULL if no widget was set * */ GtkWidget * hdy_keypad_get_entry (HdyKeypad *self) { HdyKeypadPrivate *priv; g_return_val_if_fail (HDY_IS_KEYPAD (self), NULL); priv = hdy_keypad_get_instance_private(self); return priv->entry; } /** * hdy_keypad_set_left_action: * @self: a #HdyKeypad * @widget: nullable: the widget which should be show in the left lower corner of #HdyKeypad * * Sets the widget for the left lower corner of #HdyKeypad replacing the existing widget, if NULL it just removes whatever widget is there * */ void hdy_keypad_set_left_action (HdyKeypad *self, GtkWidget *widget) { GtkWidget *old_widget; g_return_if_fail (HDY_IS_KEYPAD (self)); old_widget = gtk_grid_get_child_at (GTK_GRID (self), 0, 3); if (old_widget == widget) return; if (old_widget != NULL) gtk_container_remove (GTK_CONTAINER (self), old_widget); if (widget != NULL) gtk_grid_attach (GTK_GRID (self), widget, 0, 3, 1, 1); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LEFT_ACTION]); } /** * hdy_keypad_set_right_action: * @self: a #HdyKeypad * @widget: nullable: the widget which should be show in the right lower corner of #HdyKeypad * * Sets the widget for the right lower corner of #HdyKeypad replacing the existing widget, if NULL it just removes whatever widget is there * */ void hdy_keypad_set_right_action (HdyKeypad *self, GtkWidget *widget) { GtkWidget *old_widget; g_return_if_fail (HDY_IS_KEYPAD (self)); old_widget = gtk_grid_get_child_at (GTK_GRID (self), 2, 3); if (old_widget == widget) return; if (old_widget != NULL) gtk_container_remove (GTK_CONTAINER (self), old_widget); if (widget != NULL) gtk_grid_attach (GTK_GRID (self), widget, 2, 3, 1, 1); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_RIGHT_ACTION]); } libhandy-0.0.13/src/hdy-keypad.h000066400000000000000000000024241360136463700163770ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_KEYPAD (hdy_keypad_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyKeypad, hdy_keypad, HDY, KEYPAD, GtkGrid) /** * HdyKeypadClass: * @parent_class: The parent class */ struct _HdyKeypadClass { GtkGridClass parent_class; }; GtkWidget *hdy_keypad_new (gboolean only_digits, gboolean show_symbols); void hdy_keypad_show_symbols (HdyKeypad *self, gboolean visible); void hdy_keypad_set_entry (HdyKeypad *self, GtkEntry *entry); GtkWidget *hdy_keypad_get_entry (HdyKeypad *self); void hdy_keypad_set_left_action (HdyKeypad *self, GtkWidget *widget); void hdy_keypad_set_right_action (HdyKeypad *self, GtkWidget *widget); G_END_DECLS libhandy-0.0.13/src/hdy-keypad.ui000066400000000000000000000222361360136463700165700ustar00rootroot00000000000000 btn_0 libhandy-0.0.13/src/hdy-leaflet.c000066400000000000000000004135151360136463700165400ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "gtkprogresstrackerprivate.h" #include "hdy-animation-private.h" #include "hdy-leaflet.h" #include "hdy-shadow-helper-private.h" #include "hdy-swipeable-private.h" #include "hdy-swipe-tracker-private.h" /** * SECTION:hdy-leaflet * @short_description: An adaptive container acting like a box or a stack. * @Title: HdyLeaflet * * The #HdyLeaflet widget can display its children like a #GtkBox does or * like a #HdyLeaflet does, adapting to size changes by switching between * the two modes. * * When there is enough space the children are displayed side by side, otherwise * only one is displayed. The threshold is dictated by the preferred minimum * sizes of the children. */ /** * HdyLeafletTransitionType: * @HDY_LEAFLET_TRANSITION_TYPE_NONE: No transition * @HDY_LEAFLET_TRANSITION_TYPE_SLIDE: Slide from left, right, up or down according to the orientation, text direction and the children order * @HDY_LEAFLET_TRANSITION_TYPE_OVER: Cover the old page or uncover the new page, sliding from or towards the end according to orientation, text direction and children order * @HDY_LEAFLET_TRANSITION_TYPE_UNDER: Uncover the new page or cover the old page, sliding from or towards the start according to orientation, text direction and children order * * This enumeration value describes the possible transitions between modes and * children in a #HdyLeaflet widget. * * New values may be added to this enumeration over time. * * Since: 0.0.12 */ /** * HdyLeafletModeTransitionType: * @HDY_LEAFLET_MODE_TRANSITION_TYPE_NONE: No transition * @HDY_LEAFLET_MODE_TRANSITION_TYPE_SLIDE: Slide from left, right, up or down according to the orientation, text direction and the children order * * These enumeration values describe the possible transitions between pages in a * #HdyLeaflet widget. * * Deprecated: 0.0.12: Use #HdyLeafletTransitionType */ /** * HdyLeafletChildTransitionType: * @HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE: No transition * @HDY_LEAFLET_CHILD_TRANSITION_TYPE_CROSSFADE: A cross-fade * @HDY_LEAFLET_CHILD_TRANSITION_TYPE_SLIDE: Slide from left, right, up or down according to the orientation, text direction and the children order * @HDY_LEAFLET_CHILD_TRANSITION_TYPE_OVER: Cover the old page or uncover the new page, sliding from or towards the end according to orientation, text direction and children order * @HDY_LEAFLET_CHILD_TRANSITION_TYPE_UNDER: Uncover the new page or cover the old page, sliding from or towards the start according to orientation, text direction and children order * * These enumeration values describe the possible transitions between pages in a * #HdyLeaflet widget. * * Deprecated: 0.0.12: Use #HdyLeafletTransitionType */ enum { PROP_0, PROP_FOLD, PROP_FOLDED, PROP_HHOMOGENEOUS_FOLDED, PROP_VHOMOGENEOUS_FOLDED, PROP_HHOMOGENEOUS_UNFOLDED, PROP_VHOMOGENEOUS_UNFOLDED, PROP_VISIBLE_CHILD, PROP_VISIBLE_CHILD_NAME, PROP_TRANSITION_TYPE, PROP_MODE_TRANSITION_TYPE, PROP_MODE_TRANSITION_DURATION, PROP_CHILD_TRANSITION_TYPE, PROP_CHILD_TRANSITION_DURATION, PROP_CHILD_TRANSITION_RUNNING, PROP_INTERPOLATE_SIZE, PROP_CAN_SWIPE_BACK, PROP_CAN_SWIPE_FORWARD, /* orientable */ PROP_ORIENTATION, LAST_PROP = PROP_ORIENTATION, }; enum { CHILD_PROP_0, CHILD_PROP_NAME, CHILD_PROP_ALLOW_VISIBLE, LAST_CHILD_PROP, }; #define HDY_FOLD_MAX 2 #define GTK_ORIENTATION_MAX 2 typedef struct _HdyLeafletChildInfo HdyLeafletChildInfo; struct _HdyLeafletChildInfo { GtkWidget *widget; gchar *name; gboolean allow_visible; /* Convenience storage for per-child temporary frequently computed values. */ GtkAllocation alloc; GtkRequisition min; GtkRequisition nat; gboolean visible; }; typedef struct { GList *children; /* It is probably cheaper to store and maintain a reversed copy of the * children list that to reverse the list every time we need to allocate or * draw children for RTL languages on a horizontal leaflet. */ GList *children_reversed; HdyLeafletChildInfo *visible_child; HdyLeafletChildInfo *last_visible_child; GdkWindow* bin_window; GdkWindow* view_window; HdyFold fold; gboolean homogeneous[HDY_FOLD_MAX][GTK_ORIENTATION_MAX]; GtkOrientation orientation; gboolean move_bin_window_request; HdyLeafletTransitionType transition_type; HdySwipeTracker *tracker; struct { HdyLeafletModeTransitionType type; guint duration; gdouble current_pos; gdouble source_pos; gdouble target_pos; cairo_surface_t *start_surface; GtkAllocation start_surface_allocation; gdouble start_distance; gdouble start_progress; cairo_surface_t *end_surface; GtkAllocation end_surface_allocation; GtkAllocation end_surface_clip; gdouble end_distance; gdouble end_progress; guint tick_id; GtkProgressTracker tracker; } mode_transition; /* Child transition variables. */ struct { HdyLeafletChildTransitionType type; guint duration; gdouble progress; gdouble start_progress; gdouble end_progress; gboolean is_gesture_active; gboolean is_cancelled; cairo_surface_t *last_visible_surface; GtkAllocation last_visible_surface_allocation; guint tick_id; GtkProgressTracker tracker; gboolean first_frame_skipped; gint last_visible_widget_width; gint last_visible_widget_height; gboolean interpolate_size; gboolean can_swipe_back; gboolean can_swipe_forward; HdyLeafletChildTransitionType active_type; GtkPanDirection active_direction; } child_transition; HdyShadowHelper *shadow_helper; } HdyLeafletPrivate; static GParamSpec *props[LAST_PROP]; static GParamSpec *child_props[LAST_CHILD_PROP]; static gint HOMOGENEOUS_PROP[HDY_FOLD_MAX][GTK_ORIENTATION_MAX] = { { PROP_HHOMOGENEOUS_UNFOLDED, PROP_VHOMOGENEOUS_UNFOLDED}, { PROP_HHOMOGENEOUS_FOLDED, PROP_VHOMOGENEOUS_FOLDED}, }; static void hdy_leaflet_buildable_init (GtkBuildableIface *iface); static void hdy_leaflet_swipeable_init (HdySwipeableInterface *iface); G_DEFINE_TYPE_WITH_CODE (HdyLeaflet, hdy_leaflet, GTK_TYPE_CONTAINER, G_ADD_PRIVATE (HdyLeaflet) G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL) G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, hdy_leaflet_buildable_init) G_IMPLEMENT_INTERFACE (HDY_TYPE_SWIPEABLE, hdy_leaflet_swipeable_init)) static void free_child_info (HdyLeafletChildInfo *child_info) { g_free (child_info->name); g_free (child_info); } static HdyLeafletChildInfo * find_child_info_for_widget (HdyLeaflet *self, GtkWidget *widget) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GList *children; HdyLeafletChildInfo *child_info; for (children = priv->children; children; children = children->next) { child_info = children->data; if (child_info->widget == widget) return child_info; } return NULL; } static HdyLeafletChildInfo * find_child_info_for_name (HdyLeaflet *self, const gchar *name) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GList *children; HdyLeafletChildInfo *child_info; for (children = priv->children; children; children = children->next) { child_info = children->data; if (g_strcmp0 (child_info->name, name) == 0) return child_info; } return NULL; } static GList * get_directed_children (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); return priv->orientation == GTK_ORIENTATION_HORIZONTAL && gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL ? priv->children_reversed : priv->children; } /* Transitions that cause the bin window to move */ static inline gboolean is_window_moving_child_transition (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkPanDirection direction; gboolean is_rtl; GtkPanDirection left_or_right, right_or_left; direction = priv->child_transition.active_direction; is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; left_or_right = is_rtl ? GTK_PAN_DIRECTION_RIGHT : GTK_PAN_DIRECTION_LEFT; right_or_left = is_rtl ? GTK_PAN_DIRECTION_LEFT : GTK_PAN_DIRECTION_RIGHT; switch (priv->child_transition.active_type) { case HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE: case HDY_LEAFLET_CHILD_TRANSITION_TYPE_CROSSFADE: return FALSE; case HDY_LEAFLET_CHILD_TRANSITION_TYPE_SLIDE: return TRUE; case HDY_LEAFLET_CHILD_TRANSITION_TYPE_OVER: return direction == GTK_PAN_DIRECTION_UP || direction == left_or_right; case HDY_LEAFLET_CHILD_TRANSITION_TYPE_UNDER: return direction == GTK_PAN_DIRECTION_DOWN || direction == right_or_left; default: g_assert_not_reached (); } } /* Transitions that change direction depending on the relative order of the old and new child */ static inline gboolean is_direction_dependent_child_transition (HdyLeafletChildTransitionType transition_type) { return (transition_type == HDY_LEAFLET_CHILD_TRANSITION_TYPE_SLIDE || transition_type == HDY_LEAFLET_CHILD_TRANSITION_TYPE_OVER || transition_type == HDY_LEAFLET_CHILD_TRANSITION_TYPE_UNDER); } static GtkPanDirection get_pan_direction (HdyLeaflet *self, gboolean new_child_first) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) { if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) return new_child_first ? GTK_PAN_DIRECTION_LEFT : GTK_PAN_DIRECTION_RIGHT; else return new_child_first ? GTK_PAN_DIRECTION_RIGHT : GTK_PAN_DIRECTION_LEFT; } else return new_child_first ? GTK_PAN_DIRECTION_DOWN : GTK_PAN_DIRECTION_UP; } static gint get_bin_window_x (HdyLeaflet *self, const GtkAllocation *allocation) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); int x = 0; if (priv->child_transition.is_gesture_active || gtk_progress_tracker_get_state (&priv->child_transition.tracker) != GTK_PROGRESS_STATE_AFTER) { if (priv->child_transition.active_direction == GTK_PAN_DIRECTION_LEFT) x = allocation->width * (1 - priv->child_transition.progress); if (priv->child_transition.active_direction == GTK_PAN_DIRECTION_RIGHT) x = -allocation->width * (1 - priv->child_transition.progress); } return x; } static gint get_bin_window_y (HdyLeaflet *self, const GtkAllocation *allocation) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); int y = 0; if (priv->child_transition.is_gesture_active || gtk_progress_tracker_get_state (&priv->child_transition.tracker) != GTK_PROGRESS_STATE_AFTER) { if (priv->child_transition.active_direction == GTK_PAN_DIRECTION_UP) y = allocation->height * (1 - priv->child_transition.progress); if (priv->child_transition.active_direction == GTK_PAN_DIRECTION_DOWN) y = -allocation->height * (1 - priv->child_transition.progress); } return y; } static void move_resize_bin_window (HdyLeaflet *self, GtkAllocation *allocation, gboolean resize) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkAllocation alloc; gboolean move; if (priv->bin_window == NULL) return; if (allocation == NULL) { gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); allocation = &alloc; } move = priv->move_bin_window_request || is_window_moving_child_transition (self); if (move && resize) gdk_window_move_resize (priv->bin_window, get_bin_window_x (self, allocation), get_bin_window_y (self, allocation), allocation->width, allocation->height); else if (move) gdk_window_move (priv->bin_window, get_bin_window_x (self, allocation), get_bin_window_y (self, allocation)); else if (resize) gdk_window_resize (priv->bin_window, allocation->width, allocation->height); priv->move_bin_window_request = FALSE; } static void hdy_leaflet_child_progress_updated (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gtk_widget_queue_draw (GTK_WIDGET (self)); if (!priv->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_VERTICAL] || !priv->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_HORIZONTAL]) gtk_widget_queue_resize (GTK_WIDGET (self)); move_resize_bin_window (self, NULL, FALSE); if (!priv->child_transition.is_gesture_active && gtk_progress_tracker_get_state (&priv->child_transition.tracker) == GTK_PROGRESS_STATE_AFTER) { if (priv->child_transition.last_visible_surface != NULL) { cairo_surface_destroy (priv->child_transition.last_visible_surface); priv->child_transition.last_visible_surface = NULL; } if (priv->child_transition.is_cancelled) { if (priv->last_visible_child != NULL) { if (hdy_leaflet_get_fold (self) == HDY_FOLD_FOLDED) { gtk_widget_set_child_visible (priv->last_visible_child->widget, TRUE); gtk_widget_set_child_visible (priv->visible_child->widget, FALSE); } priv->visible_child = priv->last_visible_child; priv->last_visible_child = NULL; } g_object_freeze_notify (G_OBJECT (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD]); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD_NAME]); g_object_thaw_notify (G_OBJECT (self)); } else { if (priv->last_visible_child != NULL) { if (hdy_leaflet_get_fold (self) == HDY_FOLD_FOLDED) gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE); priv->last_visible_child = NULL; } } gtk_widget_queue_allocate (GTK_WIDGET (self)); hdy_shadow_helper_clear_cache (priv->shadow_helper); } } static gboolean hdy_leaflet_child_transition_cb (GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gdouble progress; if (priv->child_transition.first_frame_skipped) { gtk_progress_tracker_advance_frame (&priv->child_transition.tracker, gdk_frame_clock_get_frame_time (frame_clock)); progress = gtk_progress_tracker_get_ease_out_cubic (&priv->child_transition.tracker, FALSE); priv->child_transition.progress = hdy_lerp (priv->child_transition.end_progress, priv->child_transition.start_progress, progress); } else priv->child_transition.first_frame_skipped = TRUE; /* Finish animation early if not mapped anymore */ if (!gtk_widget_get_mapped (widget)) gtk_progress_tracker_finish (&priv->child_transition.tracker); hdy_leaflet_child_progress_updated (self); if (gtk_progress_tracker_get_state (&priv->child_transition.tracker) == GTK_PROGRESS_STATE_AFTER) { priv->child_transition.tick_id = 0; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TRANSITION_RUNNING]); return FALSE; } return TRUE; } static void hdy_leaflet_schedule_child_ticks (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); if (priv->child_transition.tick_id == 0) { priv->child_transition.tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), hdy_leaflet_child_transition_cb, self, NULL); if (!priv->child_transition.is_gesture_active) g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TRANSITION_RUNNING]); } } static void hdy_leaflet_unschedule_child_ticks (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); if (priv->child_transition.tick_id != 0) { gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->child_transition.tick_id); priv->child_transition.tick_id = 0; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TRANSITION_RUNNING]); } } static void hdy_leaflet_stop_child_transition (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); hdy_leaflet_unschedule_child_ticks (self); priv->child_transition.active_type = HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE; gtk_progress_tracker_finish (&priv->child_transition.tracker); if (priv->child_transition.last_visible_surface != NULL) { cairo_surface_destroy (priv->child_transition.last_visible_surface); priv->child_transition.last_visible_surface = NULL; } if (priv->last_visible_child != NULL) { gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE); priv->last_visible_child = NULL; } hdy_shadow_helper_clear_cache (priv->shadow_helper); /* Move the bin window back in place as a child transition might have moved it. */ priv->move_bin_window_request = TRUE; } static void hdy_leaflet_start_child_transition (HdyLeaflet *self, HdyLeafletChildTransitionType transition_type, guint transition_duration, GtkPanDirection transition_direction) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkWidget *widget = GTK_WIDGET (self); if (gtk_widget_get_mapped (widget) && (hdy_get_enable_animations (widget) || priv->child_transition.is_gesture_active) && transition_type != HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE && transition_duration != 0 && priv->last_visible_child != NULL && /* Don't animate child transition when a mode transition is ongoing. */ priv->mode_transition.tick_id == 0) { priv->child_transition.active_type = transition_type; priv->child_transition.active_direction = transition_direction; priv->child_transition.first_frame_skipped = FALSE; priv->child_transition.start_progress = 0; priv->child_transition.end_progress = 1; priv->child_transition.progress = 0; priv->child_transition.is_cancelled = FALSE; if (!priv->child_transition.is_gesture_active) { hdy_leaflet_schedule_child_ticks (self); gtk_progress_tracker_start (&priv->child_transition.tracker, transition_duration * 1000, 0, 1.0); } } else { hdy_leaflet_unschedule_child_ticks (self); priv->child_transition.active_type = HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE; gtk_progress_tracker_finish (&priv->child_transition.tracker); } hdy_leaflet_child_progress_updated (self); } static void set_visible_child_info (HdyLeaflet *self, HdyLeafletChildInfo *new_visible_child, HdyLeafletChildTransitionType transition_type, guint transition_duration, gboolean emit_switch_child) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkWidget *widget = GTK_WIDGET (self); GList *children; HdyLeafletChildInfo *child_info; GtkPanDirection transition_direction = GTK_PAN_DIRECTION_LEFT; /* If we are being destroyed, do not bother with transitions and * * notifications. */ if (gtk_widget_in_destruction (widget)) return; /* If none, pick first visible. */ if (new_visible_child == NULL) { for (children = priv->children; children; children = children->next) { child_info = children->data; if (gtk_widget_get_visible (child_info->widget)) { new_visible_child = child_info; break; } } } if (new_visible_child == priv->visible_child) return; /* FIXME Probably copied from Gtk Stack, should check whether it's needed. */ /* toplevel = gtk_widget_get_toplevel (widget); */ /* if (GTK_IS_WINDOW (toplevel)) { */ /* focus = gtk_window_get_focus (GTK_WINDOW (toplevel)); */ /* if (focus && */ /* priv->visible_child && */ /* priv->visible_child->widget && */ /* gtk_widget_is_ancestor (focus, priv->visible_child->widget)) { */ /* contains_focus = TRUE; */ /* if (priv->visible_child->last_focus) */ /* g_object_remove_weak_pointer (G_OBJECT (priv->visible_child->last_focus), */ /* (gpointer *)&priv->visible_child->last_focus); */ /* priv->visible_child->last_focus = focus; */ /* g_object_add_weak_pointer (G_OBJECT (priv->visible_child->last_focus), */ /* (gpointer *)&priv->visible_child->last_focus); */ /* } */ /* } */ if (priv->last_visible_child) gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE); priv->last_visible_child = NULL; if (priv->child_transition.last_visible_surface != NULL) cairo_surface_destroy (priv->child_transition.last_visible_surface); priv->child_transition.last_visible_surface = NULL; hdy_shadow_helper_clear_cache (priv->shadow_helper); if (priv->visible_child && priv->visible_child->widget) { if (gtk_widget_is_visible (widget)) { GtkAllocation allocation; priv->last_visible_child = priv->visible_child; gtk_widget_get_allocated_size (priv->last_visible_child->widget, &allocation, NULL); priv->child_transition.last_visible_widget_width = allocation.width; priv->child_transition.last_visible_widget_height = allocation.height; } else gtk_widget_set_child_visible (priv->visible_child->widget, FALSE); } /* FIXME This comes from GtkStack and should be adapted. */ /* hdy_leaflet_accessible_update_visible_child (stack, */ /* priv->visible_child ? priv->visible_child->widget : NULL, */ /* new_visible_child ? new_visible_child->widget : NULL); */ priv->visible_child = new_visible_child; if (new_visible_child) { gtk_widget_set_child_visible (new_visible_child->widget, TRUE); /* FIXME This comes from GtkStack and should be adapted. */ /* if (contains_focus) { */ /* if (new_visible_child->last_focus) */ /* gtk_widget_grab_focus (new_visible_child->last_focus); */ /* else */ /* gtk_widget_child_focus (new_visible_child->widget, GTK_DIR_TAB_FORWARD); */ /* } */ } if ((new_visible_child == NULL || priv->last_visible_child == NULL) && is_direction_dependent_child_transition (transition_type)) transition_type = HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE; else if (is_direction_dependent_child_transition (transition_type)) { gboolean new_first = FALSE; for (children = priv->children; children; children = children->next) { if (new_visible_child == children->data) { new_first = TRUE; break; } if (priv->last_visible_child == children->data) break; } transition_direction = get_pan_direction (self, new_first); } if (priv->fold == HDY_FOLD_FOLDED) { if (priv->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_HORIZONTAL] && priv->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_VERTICAL]) gtk_widget_queue_allocate (widget); else gtk_widget_queue_resize (widget); hdy_leaflet_start_child_transition (self, transition_type, transition_duration, transition_direction); } if (emit_switch_child) { gint n; children = gtk_container_get_children (GTK_CONTAINER (self)); n = g_list_index (children, new_visible_child->widget); g_list_free (children); hdy_swipeable_emit_switch_child (HDY_SWIPEABLE (self), n, transition_duration); } g_object_freeze_notify (G_OBJECT (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD]); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD_NAME]); g_object_thaw_notify (G_OBJECT (self)); } static void get_padding (GtkWidget *widget, GtkBorder *padding) { GtkStyleContext *context; GtkStateFlags state; context = gtk_widget_get_style_context (widget); state = gtk_style_context_get_state (context); gtk_style_context_get_padding (context, state, padding); } static void hdy_leaflet_set_position (HdyLeaflet *self, gdouble pos) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gboolean new_visible; GtkWidget *child; /* HdyLeafletModeTransitionType transition; */ priv->mode_transition.current_pos = pos; /* We check mode_transition.target_pos here too, because we want to ensure we set * child_visible immediately when starting a reveal operation * otherwise the child widgets will not be properly realized * after the reveal returns. */ new_visible = priv->mode_transition.current_pos != 0.0 || priv->mode_transition.target_pos != 0.0; child = hdy_leaflet_get_visible_child (self); if (child != NULL && new_visible != gtk_widget_get_child_visible (child)) gtk_widget_set_child_visible (child, new_visible); /* FIXME Copied from GtkRevealer IIRC, check whether it's useful. */ /* transition = effective_transition (self); */ /* if (transition == GTK_REVEALER_TRANSITION_TYPE_CROSSFADE) { */ /* gtk_widget_set_opacity (GTK_WIDGET (self), priv->mode_transition.current_pos); */ /* gtk_widget_queue_draw (GTK_WIDGET (self)); */ /* } */ /* else */ gtk_widget_queue_resize (GTK_WIDGET (self)); /* } */ /* if (priv->mode_transition.current_pos == priv->mode_transition.target_pos) */ /* g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_REVEALED]); */ } /* As HdyLeafletModeTransitionType is an ABI compatible subset of * HdyLeafletTransitionType, we can simply use HdyLeafletTransitionType * internally for mode transitions. */ static HdyLeafletTransitionType get_mode_transition_type (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); return priv->transition_type != HDY_LEAFLET_TRANSITION_TYPE_NONE ? priv->transition_type : (HdyLeafletTransitionType) priv->mode_transition.type; } /* As HdyLeafletChildTransitionType contains more transitions than * HdyLeafletTransitionType, and they aren't ABI compatible, it's simpler to use * HdyLeafletChildTransitionType internally for child transition. */ static HdyLeafletChildTransitionType get_old_child_transition_type (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); if (priv->transition_type == HDY_LEAFLET_TRANSITION_TYPE_NONE) return priv->child_transition.type; switch (priv->transition_type) { case HDY_LEAFLET_TRANSITION_TYPE_NONE: return HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE; case HDY_LEAFLET_TRANSITION_TYPE_SLIDE: return HDY_LEAFLET_CHILD_TRANSITION_TYPE_SLIDE; case HDY_LEAFLET_TRANSITION_TYPE_OVER: return HDY_LEAFLET_CHILD_TRANSITION_TYPE_OVER; case HDY_LEAFLET_TRANSITION_TYPE_UNDER: return HDY_LEAFLET_CHILD_TRANSITION_TYPE_UNDER; default: g_assert_not_reached (); } } static void hdy_leaflet_mode_progress_updated (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); if (gtk_progress_tracker_get_state (&priv->mode_transition.tracker) == GTK_PROGRESS_STATE_AFTER) { if (priv->mode_transition.start_surface != NULL) { cairo_surface_destroy (priv->mode_transition.start_surface); priv->mode_transition.start_surface = NULL; } if (priv->mode_transition.end_surface != NULL) { cairo_surface_destroy (priv->mode_transition.end_surface); priv->mode_transition.end_surface = NULL; } hdy_shadow_helper_clear_cache (priv->shadow_helper); } } static gboolean hdy_leaflet_mode_transition_cb (GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gdouble ease; gtk_progress_tracker_advance_frame (&priv->mode_transition.tracker, gdk_frame_clock_get_frame_time (frame_clock)); ease = gtk_progress_tracker_get_ease_out_cubic (&priv->mode_transition.tracker, FALSE); hdy_leaflet_set_position (self, priv->mode_transition.source_pos + (ease * (priv->mode_transition.target_pos - priv->mode_transition.source_pos))); hdy_leaflet_mode_progress_updated (self); if (gtk_progress_tracker_get_state (&priv->mode_transition.tracker) == GTK_PROGRESS_STATE_AFTER) { priv->mode_transition.tick_id = 0; return FALSE; } return TRUE; } static void hdy_leaflet_start_mode_transition (HdyLeaflet *self, gdouble target) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkWidget *widget = GTK_WIDGET (self); if (priv->mode_transition.target_pos == target) return; priv->mode_transition.target_pos = target; /* FIXME PROP_REVEAL_CHILD needs to be implemented. */ /* g_object_notify_by_pspec (G_OBJECT (revealer), props[PROP_REVEAL_CHILD]); */ hdy_leaflet_stop_child_transition (self); if (gtk_widget_get_mapped (widget) && priv->mode_transition.duration != 0 && get_mode_transition_type (self) != HDY_LEAFLET_TRANSITION_TYPE_NONE && hdy_get_enable_animations (widget)) { priv->mode_transition.source_pos = priv->mode_transition.current_pos; if (priv->mode_transition.tick_id == 0) priv->mode_transition.tick_id = gtk_widget_add_tick_callback (widget, hdy_leaflet_mode_transition_cb, self, NULL); gtk_progress_tracker_start (&priv->mode_transition.tracker, priv->mode_transition.duration * 1000, 0, 1.0); } else hdy_leaflet_set_position (self, target); } /* FIXME Use this to stop the mode transition animation when it makes sense (see * * GtkRevealer for exmples). */ /* static void */ /* hdy_leaflet_stop_mode_animation (HdyLeaflet *self) */ /* { */ /* HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); */ /* if (priv->mode_transition.current_pos != priv->mode_transition.target_pos) { */ /* priv->mode_transition.current_pos = priv->mode_transition.target_pos; */ /* g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_REVEALED]); */ /* } */ /* if (priv->mode_transition.tick_id != 0) { */ /* gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->mode_transition.tick_id); */ /* priv->mode_transition.tick_id = 0; */ /* } */ /* } */ /** * hdy_leaflet_get_fold: * @self: a #HdyLeaflet * * Gets the fold of @self. * * Returns: the fold of @self. */ HdyFold hdy_leaflet_get_fold (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); priv = hdy_leaflet_get_instance_private (self); return priv->fold; } static void hdy_leaflet_set_fold (HdyLeaflet *self, HdyFold fold) { HdyLeafletPrivate *priv; g_return_if_fail (HDY_IS_LEAFLET (self)); priv = hdy_leaflet_get_instance_private (self); if (priv->fold == fold) return; priv->fold = fold; if (fold) hdy_leaflet_start_mode_transition (self, 0.0); else hdy_leaflet_start_mode_transition (self, 1.0); g_object_freeze_notify (G_OBJECT (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FOLD]); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FOLDED]); g_object_thaw_notify (G_OBJECT (self)); } /** * hdy_leaflet_set_homogeneous: * @self: a #HdyLeaflet * @fold: the fold * @orientation: the orientation * @homogeneous: %TRUE to make @self homogeneous * * Sets the #HdyLeaflet to be homogeneous or not for the given fold and orientation. * If it is homogeneous, the #HdyLeaflet will request the same * width or height for all its children depending on the orientation. * If it isn't and it is folded, the leaflet may change width or height * when a different child becomes visible. */ void hdy_leaflet_set_homogeneous (HdyLeaflet *self, HdyFold fold, GtkOrientation orientation, gboolean homogeneous) { HdyLeafletPrivate *priv; g_return_if_fail (HDY_IS_LEAFLET (self)); priv = hdy_leaflet_get_instance_private (self); homogeneous = !!homogeneous; if (priv->homogeneous[fold][orientation] == homogeneous) return; priv->homogeneous[fold][orientation] = homogeneous; if (gtk_widget_get_visible (GTK_WIDGET (self))) gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify_by_pspec (G_OBJECT (self), props[HOMOGENEOUS_PROP[fold][orientation]]); } /** * hdy_leaflet_get_homogeneous: * @self: a #HdyLeaflet * @fold: the fold * @orientation: the orientation * * Gets whether @self is homogeneous for the given fold and orientation. * See hdy_leaflet_set_homogeneous(). * * Returns: whether @self is homogeneous for the given fold and orientation. */ gboolean hdy_leaflet_get_homogeneous (HdyLeaflet *self, HdyFold fold, GtkOrientation orientation) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); priv = hdy_leaflet_get_instance_private (self); return priv->homogeneous[fold][orientation]; } /** * hdy_leaflet_get_transition_type: * @self: a #HdyLeaflet * * Gets the type of animation that will be used * for transitions between modes and children in @self. * * Returns: the current transition type of @self * * Since: 0.0.12 */ HdyLeafletTransitionType hdy_leaflet_get_transition_type (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), HDY_LEAFLET_TRANSITION_TYPE_NONE); priv = hdy_leaflet_get_instance_private (self); return priv->transition_type; } /** * hdy_leaflet_set_transition_type: * @self: a #HdyLeaflet * @transition: the new transition type * * Sets the type of animation that will be used for transitions between modes * and children in @self. * * The transition type can be changed without problems at runtime, so it is * possible to change the animation based on the mode or child that is about to * become current. * * Since: 0.0.12 */ void hdy_leaflet_set_transition_type (HdyLeaflet *self, HdyLeafletTransitionType transition) { HdyLeafletPrivate *priv; g_return_if_fail (HDY_IS_LEAFLET (self)); priv = hdy_leaflet_get_instance_private (self); if (priv->transition_type == transition) return; priv->transition_type = transition; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_TYPE]); } /** * hdy_leaflet_get_mode_transition_type: * @self: a #HdyLeaflet * * Gets the type of animation that will be used * for transitions between modes in @self. * * Returns: the current mode transition type of @self * * Deprecated: 0.0.12: Use hdy_leaflet_get_transition_type() */ HdyLeafletModeTransitionType hdy_leaflet_get_mode_transition_type (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), HDY_LEAFLET_MODE_TRANSITION_TYPE_NONE); priv = hdy_leaflet_get_instance_private (self); return priv->mode_transition.type; } /** * hdy_leaflet_set_mode_transition_type: * @self: a #HdyLeaflet * @transition: the new transition type * * Sets the type of animation that will be used for * transitions between modes in @self. * * The transition type can be changed without problems * at runtime, so it is possible to change the animation * based on the mode that is about to become current. * * Deprecated: 0.0.12: Use hdy_leaflet_set_transition_type() */ void hdy_leaflet_set_mode_transition_type (HdyLeaflet *self, HdyLeafletModeTransitionType transition) { HdyLeafletPrivate *priv; g_return_if_fail (HDY_IS_LEAFLET (self)); priv = hdy_leaflet_get_instance_private (self); if (priv->mode_transition.type == transition) return; priv->mode_transition.type = transition; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_MODE_TRANSITION_TYPE]); } /** * hdy_leaflet_get_mode_transition_duration: * @self: a #HdyLeaflet * * Returns the amount of time (in milliseconds) that * transitions between modes in @self will take. * * Returns: the mode transition duration */ guint hdy_leaflet_get_mode_transition_duration (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), 0); priv = hdy_leaflet_get_instance_private (self); return priv->mode_transition.duration; } /** * hdy_leaflet_set_mode_transition_duration: * @self: a #HdyLeaflet * @duration: the new duration, in milliseconds * * Sets the duration that transitions between modes in @self * will take. */ void hdy_leaflet_set_mode_transition_duration (HdyLeaflet *self, guint duration) { HdyLeafletPrivate *priv; g_return_if_fail (HDY_IS_LEAFLET (self)); priv = hdy_leaflet_get_instance_private (self); if (priv->mode_transition.duration == duration) return; priv->mode_transition.duration = duration; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_MODE_TRANSITION_DURATION]); } /** * hdy_leaflet_get_child_transition_type: * @self: a #HdyLeaflet * * Gets the type of animation that will be used * for transitions between children in @self. * * Returns: the current mode transition type of @self * * Deprecated: 0.0.12: Use hdy_leaflet_get_transition_type() */ HdyLeafletChildTransitionType hdy_leaflet_get_child_transition_type (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE); priv = hdy_leaflet_get_instance_private (self); return priv->child_transition.type; } /** * hdy_leaflet_set_child_transition_type: * @self: a #HdyLeaflet * @transition: the new transition type * * Sets the type of animation that will be used for * transitions between children in @self. * * The transition type can be changed without problems * at runtime, so it is possible to change the animation * based on the child that is about to become current. * * Deprecated: 0.0.12: Use hdy_leaflet_set_transition_type() */ void hdy_leaflet_set_child_transition_type (HdyLeaflet *self, HdyLeafletChildTransitionType transition) { HdyLeafletPrivate *priv; g_return_if_fail (HDY_IS_LEAFLET (self)); priv = hdy_leaflet_get_instance_private (self); if (priv->child_transition.type == transition) return; priv->child_transition.type = transition; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TRANSITION_TYPE]); } /** * hdy_leaflet_get_child_transition_duration: * @self: a #HdyLeaflet * * Returns the amount of time (in milliseconds) that * transitions between children in @self will take. * * Returns: the mode transition duration */ guint hdy_leaflet_get_child_transition_duration (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), 0); priv = hdy_leaflet_get_instance_private (self); return priv->child_transition.duration; } /** * hdy_leaflet_set_child_transition_duration: * @self: a #HdyLeaflet * @duration: the new duration, in milliseconds * * Sets the duration that transitions between children in @self * will take. */ void hdy_leaflet_set_child_transition_duration (HdyLeaflet *self, guint duration) { HdyLeafletPrivate *priv; g_return_if_fail (HDY_IS_LEAFLET (self)); priv = hdy_leaflet_get_instance_private (self); if (priv->child_transition.duration == duration) return; priv->child_transition.duration = duration; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TRANSITION_DURATION]); } /** * hdy_leaflet_get_visible_child: * @self: a #HdyLeaflet * * Get the visible child widget. * * Returns: (transfer none): the visible child widget */ GtkWidget * hdy_leaflet_get_visible_child (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), NULL); priv = hdy_leaflet_get_instance_private (self); if (priv->visible_child == NULL) return NULL; return priv->visible_child->widget; } void hdy_leaflet_set_visible_child (HdyLeaflet *self, GtkWidget *visible_child) { HdyLeafletPrivate *priv; HdyLeafletChildInfo *child_info; gboolean contains_child; g_return_if_fail (HDY_IS_LEAFLET (self)); g_return_if_fail (GTK_IS_WIDGET (visible_child)); priv = hdy_leaflet_get_instance_private (self); child_info = find_child_info_for_widget (self, visible_child); contains_child = child_info != NULL; g_return_if_fail (contains_child); set_visible_child_info (self, child_info, get_old_child_transition_type (self), priv->child_transition.duration, TRUE); } const gchar * hdy_leaflet_get_visible_child_name (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), NULL); priv = hdy_leaflet_get_instance_private (self); if (priv->visible_child == NULL) return NULL; return priv->visible_child->name; } void hdy_leaflet_set_visible_child_name (HdyLeaflet *self, const gchar *name) { HdyLeafletPrivate *priv; HdyLeafletChildInfo *child_info; gboolean contains_child; g_return_if_fail (HDY_IS_LEAFLET (self)); g_return_if_fail (name != NULL); priv = hdy_leaflet_get_instance_private (self); child_info = find_child_info_for_name (self, name); contains_child = child_info != NULL; g_return_if_fail (contains_child); set_visible_child_info (self, child_info, get_old_child_transition_type (self), priv->child_transition.duration, TRUE); } /** * hdy_leaflet_get_child_transition_running: * @self: a #HdyLeaflet * * Returns whether @self is currently in a transition from one page to * another. * * Returns: %TRUE if the transition is currently running, %FALSE otherwise. */ gboolean hdy_leaflet_get_child_transition_running (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); priv = hdy_leaflet_get_instance_private (self); return (priv->child_transition.tick_id != 0 || priv->child_transition.is_gesture_active); } /** * hdy_leaflet_set_interpolate_size: * @self: a #HdyLeaflet * @interpolate_size: the new value * * Sets whether or not @self will interpolate its size when * changing the visible child. If the #HdyLeaflet:interpolate-size * property is set to %TRUE, @stack will interpolate its size between * the current one and the one it'll take after changing the * visible child, according to the set transition duration. */ void hdy_leaflet_set_interpolate_size (HdyLeaflet *self, gboolean interpolate_size) { HdyLeafletPrivate *priv; g_return_if_fail (HDY_IS_LEAFLET (self)); priv = hdy_leaflet_get_instance_private (self); interpolate_size = !!interpolate_size; if (priv->child_transition.interpolate_size == interpolate_size) return; priv->child_transition.interpolate_size = interpolate_size; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INTERPOLATE_SIZE]); } /** * hdy_leaflet_get_interpolate_size: * @self: a #HdyLeaflet * * Returns wether the #HdyLeaflet is set up to interpolate between * the sizes of children on page switch. * * Returns: %TRUE if child sizes are interpolated */ gboolean hdy_leaflet_get_interpolate_size (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); priv = hdy_leaflet_get_instance_private (self); return priv->child_transition.interpolate_size; } /** * hdy_leaflet_set_can_swipe_back: * @self: a #HdyLeaflet * @can_swipe_back: the new value * * Sets whether or not @self allows switching to the previous child that has * 'allow-visible' child property set to %TRUE via a swipe gesture * * Since: 0.0.12 */ void hdy_leaflet_set_can_swipe_back (HdyLeaflet *self, gboolean can_swipe_back) { HdyLeafletPrivate *priv; g_return_if_fail (HDY_IS_LEAFLET (self)); priv = hdy_leaflet_get_instance_private (self); can_swipe_back = !!can_swipe_back; if (priv->child_transition.can_swipe_back == can_swipe_back) return; priv->child_transition.can_swipe_back = can_swipe_back; hdy_swipe_tracker_set_enabled (priv->tracker, can_swipe_back || priv->child_transition.can_swipe_forward); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CAN_SWIPE_BACK]); } /** * hdy_leaflet_get_can_swipe_back * @self: a #HdyLeaflet * * Returns whether the #HdyLeaflet allows swiping to the previous child. * * Returns: %TRUE if back swipe is enabled. * * Since: 0.0.12 */ gboolean hdy_leaflet_get_can_swipe_back (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); priv = hdy_leaflet_get_instance_private (self); return priv->child_transition.can_swipe_back; } /** * hdy_leaflet_set_can_swipe_forward: * @self: a #HdyLeaflet * @can_swipe_forward: the new value * * Sets whether or not @self allows switching to the next child that has * 'allow-visible' child property set to %TRUE via a swipe gesture. * * Since: 0.0.12 */ void hdy_leaflet_set_can_swipe_forward (HdyLeaflet *self, gboolean can_swipe_forward) { HdyLeafletPrivate *priv; g_return_if_fail (HDY_IS_LEAFLET (self)); priv = hdy_leaflet_get_instance_private (self); can_swipe_forward = !!can_swipe_forward; if (priv->child_transition.can_swipe_forward == can_swipe_forward) return; priv->child_transition.can_swipe_forward = can_swipe_forward; hdy_swipe_tracker_set_enabled (priv->tracker, priv->child_transition.can_swipe_back || can_swipe_forward); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CAN_SWIPE_FORWARD]); } /** * hdy_leaflet_get_can_swipe_forward * @self: a #HdyLeaflet * * Returns whether the #HdyLeaflet allows swiping to the next child. * * Returns: %TRUE if back swipe is enabled. * * Since: 0.0.12 */ gboolean hdy_leaflet_get_can_swipe_forward (HdyLeaflet *self) { HdyLeafletPrivate *priv; g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); priv = hdy_leaflet_get_instance_private (self); return priv->child_transition.can_swipe_forward; } static void get_preferred_size (gint *min, gint *nat, gboolean same_orientation, gboolean homogeneous_folded, gboolean homogeneous_unfolded, gint visible_children, gdouble visible_child_progress, gint sum_nat, gint max_min, gint max_nat, gint visible_min, gint last_visible_min) { if (same_orientation) { *min = homogeneous_folded ? max_min : hdy_lerp (visible_min, last_visible_min, visible_child_progress); *nat = homogeneous_unfolded ? max_nat * visible_children : sum_nat; } else { *min = homogeneous_folded ? max_min : hdy_lerp (visible_min, last_visible_min, visible_child_progress); *nat = max_nat; } } /* This private method is prefixed by the call name because it will be a virtual * method in GTK 4. */ static void hdy_leaflet_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GList *children; HdyLeafletChildInfo *child_info; gint visible_children; gdouble visible_child_progress; gint child_min, max_min, visible_min, last_visible_min; gint child_nat, max_nat, sum_nat; void (*get_preferred_size_static) (GtkWidget *widget, gint *minimum_width, gint *natural_width); void (*get_preferred_size_for_size) (GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width); get_preferred_size_static = orientation == GTK_ORIENTATION_HORIZONTAL ? gtk_widget_get_preferred_width : gtk_widget_get_preferred_height; get_preferred_size_for_size = orientation == GTK_ORIENTATION_HORIZONTAL ? gtk_widget_get_preferred_width_for_height : gtk_widget_get_preferred_height_for_width; visible_children = 0; child_min = max_min = visible_min = last_visible_min = 0; child_nat = max_nat = sum_nat = 0; for (children = priv->children; children; children = children->next) { child_info = children->data; if (child_info->widget == NULL || !gtk_widget_get_visible (child_info->widget)) continue; visible_children++; if (for_size < 0) get_preferred_size_static (child_info->widget, &child_min, &child_nat); else get_preferred_size_for_size (child_info->widget, for_size, &child_min, &child_nat); max_min = MAX (max_min, child_min); max_nat = MAX (max_nat, child_nat); sum_nat += child_nat; } if (priv->visible_child != NULL) { if (for_size < 0) get_preferred_size_static (priv->visible_child->widget, &visible_min, NULL); else get_preferred_size_for_size (priv->visible_child->widget, for_size, &visible_min, NULL); } if (priv->last_visible_child != NULL) { if (for_size < 0) get_preferred_size_static (priv->last_visible_child->widget, &last_visible_min, NULL); else get_preferred_size_for_size (priv->last_visible_child->widget, for_size, &last_visible_min, NULL); } visible_child_progress = priv->child_transition.interpolate_size ? priv->child_transition.progress : 1.0; get_preferred_size (minimum, natural, gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == orientation, priv->homogeneous[HDY_FOLD_FOLDED][orientation], priv->homogeneous[HDY_FOLD_UNFOLDED][orientation], visible_children, visible_child_progress, sum_nat, max_min, max_nat, visible_min, last_visible_min); } static void hdy_leaflet_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width) { hdy_leaflet_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, minimum_width, natural_width, NULL, NULL); } static void hdy_leaflet_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height) { hdy_leaflet_measure (widget, GTK_ORIENTATION_VERTICAL, -1, minimum_height, natural_height, NULL, NULL); } static void hdy_leaflet_get_preferred_width_for_height (GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width) { hdy_leaflet_measure (widget, GTK_ORIENTATION_HORIZONTAL, height, minimum_width, natural_width, NULL, NULL); } static void hdy_leaflet_get_preferred_height_for_width (GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height) { hdy_leaflet_measure (widget, GTK_ORIENTATION_VERTICAL, width, minimum_height, natural_height, NULL, NULL); } static void hdy_leaflet_size_allocate_folded (GtkWidget *widget, GtkAllocation *allocation) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkOrientation orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)); GList *directed_children, *children; HdyLeafletChildInfo *child_info, *visible_child; gint start_size, end_size, visible_size; gint remaining_start_size, remaining_end_size, remaining_size; gint current_pad; gint max_child_size = 0; gboolean box_homogeneous; HdyLeafletTransitionType mode_transition_type; GtkTextDirection direction; gboolean under; directed_children = get_directed_children (self); visible_child = priv->visible_child; for (children = directed_children; children; children = children->next) { child_info = children->data; if (!child_info->widget) continue; if (child_info->widget == visible_child->widget) continue; if (priv->last_visible_child && child_info->widget == priv->last_visible_child->widget) continue; gtk_widget_set_child_visible (child_info->widget, FALSE); } if (visible_child->widget == NULL) return; /* FIXME is this needed? */ if (!gtk_widget_get_visible (visible_child->widget)) { gtk_widget_set_child_visible (visible_child->widget, FALSE); return; } gtk_widget_set_child_visible (visible_child->widget, TRUE); mode_transition_type = get_mode_transition_type (self); /* Avoid useless computations and allow visible child transitions. */ if (priv->mode_transition.current_pos <= 0.0) mode_transition_type = HDY_LEAFLET_TRANSITION_TYPE_NONE; switch (mode_transition_type) { case HDY_LEAFLET_TRANSITION_TYPE_NONE: /* Child transitions should be applied only when folded and when no mode * transition is ongoing. */ for (children = directed_children; children; children = children->next) { child_info = children->data; if (child_info != visible_child && child_info != priv->last_visible_child) { child_info->visible = FALSE; continue; } child_info->alloc.x = 0; child_info->alloc.y = 0; child_info->alloc.width = allocation->width; child_info->alloc.height = allocation->height; child_info->visible = TRUE; } break; case HDY_LEAFLET_TRANSITION_TYPE_SLIDE: case HDY_LEAFLET_TRANSITION_TYPE_OVER: case HDY_LEAFLET_TRANSITION_TYPE_UNDER: /* Compute visible child size. */ visible_size = orientation == GTK_ORIENTATION_HORIZONTAL ? MIN (allocation->width, MAX (visible_child->nat.width, (gint) (allocation->width * (1.0 - priv->mode_transition.current_pos)))) : MIN (allocation->height, MAX (visible_child->nat.height, (gint) (allocation->height * (1.0 - priv->mode_transition.current_pos)))); /* Compute homogeneous box child size. */ box_homogeneous = (priv->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_HORIZONTAL] && orientation == GTK_ORIENTATION_HORIZONTAL) || (priv->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_VERTICAL] && orientation == GTK_ORIENTATION_VERTICAL); if (box_homogeneous) { for (children = directed_children; children; children = children->next) { child_info = children->data; max_child_size = orientation == GTK_ORIENTATION_HORIZONTAL ? MAX (max_child_size, child_info->nat.width) : MAX (max_child_size, child_info->nat.height); } } /* Compute the start size. */ start_size = 0; for (children = directed_children; children; children = children->next) { child_info = children->data; if (child_info == visible_child) break; start_size += orientation == GTK_ORIENTATION_HORIZONTAL ? (box_homogeneous ? max_child_size : child_info->nat.width) : (box_homogeneous ? max_child_size : child_info->nat.height); } /* Compute the end size. */ end_size = 0; for (children = g_list_last (directed_children); children; children = children->prev) { child_info = children->data; if (child_info == visible_child) break; end_size += orientation == GTK_ORIENTATION_HORIZONTAL ? (box_homogeneous ? max_child_size : child_info->nat.width) : (box_homogeneous ? max_child_size : child_info->nat.height); } /* Compute pads. */ remaining_size = orientation == GTK_ORIENTATION_HORIZONTAL ? allocation->width - visible_size : allocation->height - visible_size; remaining_start_size = (gint) (remaining_size * ((gdouble) start_size / (gdouble) (start_size + end_size))); remaining_end_size = remaining_size - remaining_start_size; /* Store start and end allocations. */ switch (orientation) { case GTK_ORIENTATION_HORIZONTAL: direction = gtk_widget_get_direction (GTK_WIDGET (self)); under = (mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_OVER && direction == GTK_TEXT_DIR_LTR) || (mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_UNDER && direction == GTK_TEXT_DIR_RTL); priv->mode_transition.start_surface_allocation.width = under ? remaining_size : start_size; priv->mode_transition.start_surface_allocation.height = allocation->height; priv->mode_transition.start_surface_allocation.x = under ? 0 : remaining_start_size - start_size; priv->mode_transition.start_surface_allocation.y = 0; priv->mode_transition.start_progress = under ? (gdouble) remaining_size / start_size : 1; under = (mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_UNDER && direction == GTK_TEXT_DIR_LTR) || (mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_OVER && direction == GTK_TEXT_DIR_RTL); priv->mode_transition.end_surface_allocation.width = end_size; priv->mode_transition.end_surface_allocation.height = allocation->height; priv->mode_transition.end_surface_allocation.x = under ? allocation->width - end_size : remaining_start_size + visible_size; priv->mode_transition.end_surface_allocation.y = 0; priv->mode_transition.end_surface_clip.width = end_size; priv->mode_transition.end_surface_clip.height = priv->mode_transition.end_surface_allocation.height; priv->mode_transition.end_surface_clip.x = remaining_start_size + visible_size; priv->mode_transition.end_surface_clip.y = priv->mode_transition.end_surface_allocation.y; priv->mode_transition.end_progress = under ? (gdouble) remaining_end_size / end_size : 1; break; case GTK_ORIENTATION_VERTICAL: under = mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_OVER; priv->mode_transition.start_surface_allocation.width = allocation->width; priv->mode_transition.start_surface_allocation.height = under ? remaining_size : start_size; priv->mode_transition.start_surface_allocation.x = 0; priv->mode_transition.start_surface_allocation.y = under ? 0 : remaining_start_size - start_size; priv->mode_transition.start_progress = under ? (gdouble) remaining_size / start_size : 1; under = mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_UNDER; priv->mode_transition.end_surface_allocation.width = allocation->width; priv->mode_transition.end_surface_allocation.height = end_size; priv->mode_transition.end_surface_allocation.x = 0; priv->mode_transition.end_surface_allocation.y = remaining_start_size + visible_size; priv->mode_transition.end_surface_clip.width = priv->mode_transition.end_surface_allocation.width; priv->mode_transition.end_surface_clip.height = end_size; priv->mode_transition.end_surface_clip.x = priv->mode_transition.end_surface_allocation.x; priv->mode_transition.end_surface_clip.y = remaining_start_size + visible_size; priv->mode_transition.end_progress = under ? (gdouble) remaining_end_size / end_size : 1; break; default: g_assert_not_reached (); } priv->mode_transition.start_distance = start_size; priv->mode_transition.end_distance = end_size; /* Allocate visible child. */ if (orientation == GTK_ORIENTATION_HORIZONTAL) { visible_child->alloc.width = visible_size; visible_child->alloc.height = allocation->height; visible_child->alloc.x = remaining_start_size; visible_child->alloc.y = 0; visible_child->visible = TRUE; } else { visible_child->alloc.width = allocation->width; visible_child->alloc.height = visible_size; visible_child->alloc.x = 0; visible_child->alloc.y = remaining_start_size; visible_child->visible = TRUE; } /* Allocate starting children. */ if (orientation == GTK_ORIENTATION_HORIZONTAL) current_pad = -priv->mode_transition.start_surface_allocation.x; else current_pad = -priv->mode_transition.start_surface_allocation.y; for (children = directed_children; children; children = children->next) { child_info = children->data; if (child_info == visible_child) break; if (orientation == GTK_ORIENTATION_HORIZONTAL) { child_info->alloc.width = box_homogeneous ? max_child_size : child_info->nat.width; child_info->alloc.height = allocation->height; child_info->alloc.x = -current_pad; child_info->alloc.y = 0; child_info->visible = child_info->alloc.x + child_info->alloc.width > 0; current_pad -= child_info->alloc.width; } else { child_info->alloc.width = allocation->width; child_info->alloc.height = box_homogeneous ? max_child_size : child_info->nat.height; child_info->alloc.x = 0; child_info->alloc.y = -current_pad; child_info->visible = child_info->alloc.y + child_info->alloc.height > 0; current_pad -= child_info->alloc.height; } } /* Allocate ending children. */ if (orientation == GTK_ORIENTATION_HORIZONTAL) current_pad = priv->mode_transition.end_surface_allocation.x; else current_pad = priv->mode_transition.end_surface_allocation.y; for (children = g_list_last (directed_children); children; children = children->prev) { child_info = children->data; if (child_info == visible_child) break; if (orientation == GTK_ORIENTATION_HORIZONTAL) { current_pad -= child_info->alloc.width; child_info->alloc.width = box_homogeneous ? max_child_size : child_info->nat.width; child_info->alloc.height = allocation->height; child_info->alloc.x = current_pad; child_info->alloc.y = 0; child_info->visible = child_info->alloc.x < allocation->width; } else { current_pad -= child_info->alloc.height; child_info->alloc.width = allocation->width; child_info->alloc.height = box_homogeneous ? max_child_size : child_info->nat.height; child_info->alloc.x = 0; child_info->alloc.y = current_pad; child_info->visible = child_info->alloc.y < allocation->height; } } break; default: g_assert_not_reached (); } } static void hdy_leaflet_size_allocate_unfolded (GtkWidget *widget, GtkAllocation *allocation) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkOrientation orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)); GtkAllocation remaining_alloc; GList *directed_children, *children; HdyLeafletChildInfo *child_info, *visible_child; gint homogeneous_size = 0, min_size, extra_size; gint per_child_extra, n_extra_widgets; gint n_visible_children, n_expand_children; gint start_pad = 0, end_pad = 0; gboolean box_homogeneous; HdyLeafletTransitionType mode_transition_type; GtkTextDirection direction; gboolean under; directed_children = get_directed_children (self); visible_child = priv->visible_child; box_homogeneous = (priv->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_HORIZONTAL] && orientation == GTK_ORIENTATION_HORIZONTAL) || (priv->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_VERTICAL] && orientation == GTK_ORIENTATION_VERTICAL); n_visible_children = n_expand_children = 0; for (children = directed_children; children; children = children->next) { child_info = children->data; child_info->visible = child_info->widget != NULL && gtk_widget_get_visible (child_info->widget); if (child_info->visible) { n_visible_children++; if (gtk_widget_compute_expand (child_info->widget, orientation)) n_expand_children++; } else { child_info->min.width = child_info->min.height = 0; child_info->nat.width = child_info->nat.height = 0; } } /* Compute repartition of extra space. */ if (box_homogeneous) { if (orientation == GTK_ORIENTATION_HORIZONTAL) { homogeneous_size = allocation->width / n_visible_children; n_expand_children = allocation->width % n_visible_children; min_size = allocation->width - n_expand_children; } else { homogeneous_size = allocation->height / n_visible_children; n_expand_children = allocation->height % n_visible_children; min_size = allocation->height - n_expand_children; } } else { min_size = 0; if (orientation == GTK_ORIENTATION_HORIZONTAL) { for (children = directed_children; children; children = children->next) { child_info = children->data; min_size += child_info->nat.width; } } else { for (children = directed_children; children; children = children->next) { child_info = children->data; min_size += child_info->nat.height; } } } remaining_alloc.x = 0; remaining_alloc.y = 0; remaining_alloc.width = allocation->width; remaining_alloc.height = allocation->height; extra_size = orientation == GTK_ORIENTATION_HORIZONTAL ? remaining_alloc.width - min_size : remaining_alloc.height - min_size; per_child_extra = 0, n_extra_widgets = 0; if (n_expand_children > 0) { per_child_extra = extra_size / n_expand_children; n_extra_widgets = extra_size % n_expand_children; } /* Compute children allocation */ for (children = directed_children; children; children = children->next) { child_info = children->data; if (!child_info->visible) continue; child_info->alloc.x = remaining_alloc.x; child_info->alloc.y = remaining_alloc.y; if (orientation == GTK_ORIENTATION_HORIZONTAL) { if (box_homogeneous) { child_info->alloc.width = homogeneous_size; if (n_extra_widgets > 0) { child_info->alloc.width++; n_extra_widgets--; } } else { child_info->alloc.width = child_info->nat.width; if (gtk_widget_compute_expand (child_info->widget, orientation)) { child_info->alloc.width += per_child_extra; if (n_extra_widgets > 0) { child_info->alloc.width++; n_extra_widgets--; } } } child_info->alloc.height = remaining_alloc.height; remaining_alloc.x += child_info->alloc.width; remaining_alloc.width -= child_info->alloc.width; } else { if (box_homogeneous) { child_info->alloc.height = homogeneous_size; if (n_extra_widgets > 0) { child_info->alloc.height++; n_extra_widgets--; } } else { child_info->alloc.height = child_info->nat.height; if (gtk_widget_compute_expand (child_info->widget, orientation)) { child_info->alloc.height += per_child_extra; if (n_extra_widgets > 0) { child_info->alloc.height++; n_extra_widgets--; } } } child_info->alloc.width = remaining_alloc.width; remaining_alloc.y += child_info->alloc.height; remaining_alloc.height -= child_info->alloc.height; } } /* Apply animations. */ if (orientation == GTK_ORIENTATION_HORIZONTAL) { start_pad = (gint) ((visible_child->alloc.x) * (1.0 - priv->mode_transition.current_pos)); end_pad = (gint) ((allocation->width - (visible_child->alloc.x + visible_child->alloc.width)) * (1.0 - priv->mode_transition.current_pos)); priv->mode_transition.start_distance = visible_child->alloc.x; priv->mode_transition.end_distance = allocation->width - (visible_child->alloc.x + visible_child->alloc.width); } else { start_pad = (gint) ((visible_child->alloc.y) * (1.0 - priv->mode_transition.current_pos)); end_pad = (gint) ((allocation->height - (visible_child->alloc.y + visible_child->alloc.height)) * (1.0 - priv->mode_transition.current_pos)); priv->mode_transition.start_distance = visible_child->alloc.y; priv->mode_transition.end_distance = allocation->height - (visible_child->alloc.y + visible_child->alloc.height); } mode_transition_type = get_mode_transition_type (self); direction = gtk_widget_get_direction (GTK_WIDGET (self)); if (orientation == GTK_ORIENTATION_HORIZONTAL) under = (mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_OVER && direction == GTK_TEXT_DIR_LTR) || (mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_UNDER && direction == GTK_TEXT_DIR_RTL); else under = mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_OVER; for (children = directed_children; children; children = children->next) { child_info = children->data; if (child_info == visible_child) break; if (!child_info->visible) continue; if (under) continue; if (orientation == GTK_ORIENTATION_HORIZONTAL) child_info->alloc.x -= start_pad; else child_info->alloc.y -= start_pad; } priv->mode_transition.start_progress = under ? priv->mode_transition.current_pos : 1; if (orientation == GTK_ORIENTATION_HORIZONTAL) under = (mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_UNDER && direction == GTK_TEXT_DIR_LTR) || (mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_OVER && direction == GTK_TEXT_DIR_RTL); else under = mode_transition_type == HDY_LEAFLET_TRANSITION_TYPE_UNDER; for (children = g_list_last (directed_children); children; children = children->prev) { child_info = children->data; if (child_info == visible_child) break; if (!child_info->visible) continue; if (under) continue; if (orientation == GTK_ORIENTATION_HORIZONTAL) child_info->alloc.x += end_pad; else child_info->alloc.y += end_pad; } priv->mode_transition.end_progress = under ? priv->mode_transition.current_pos : 1; if (orientation == GTK_ORIENTATION_HORIZONTAL) { visible_child->alloc.x -= start_pad; visible_child->alloc.width += start_pad + end_pad; } else { visible_child->alloc.y -= start_pad; visible_child->alloc.height += start_pad + end_pad; } } static void hdy_leaflet_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkOrientation orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)); GList *directed_children, *children; HdyLeafletChildInfo *child_info; gint nat_box_size, nat_max_size, visible_children; gboolean folded; directed_children = get_directed_children (self); gtk_widget_set_allocation (widget, allocation); if (gtk_widget_get_realized (widget)) { gdk_window_move_resize (priv->view_window, allocation->x, allocation->y, allocation->width, allocation->height); move_resize_bin_window (self, allocation, TRUE); } /* Prepare children information. */ for (children = directed_children; children; children = children->next) { child_info = children->data; gtk_widget_get_preferred_size (child_info->widget, &child_info->min, &child_info->nat); child_info->alloc.x = child_info->alloc.y = child_info->alloc.width = child_info->alloc.height = 0; child_info->visible = FALSE; } /* Check whether the children should be stacked or not. */ nat_box_size = 0; nat_max_size = 0; visible_children = 0; if (orientation == GTK_ORIENTATION_HORIZONTAL) { for (children = directed_children; children; children = children->next) { child_info = children->data; /* FIXME Check the child is visible. */ if (!child_info->widget) continue; nat_box_size += child_info->nat.width; nat_max_size = MAX (nat_max_size, child_info->nat.width); visible_children++; } if (priv->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_HORIZONTAL]) nat_box_size = nat_max_size * visible_children; folded = allocation->width < nat_box_size; } else { for (children = directed_children; children; children = children->next) { child_info = children->data; /* FIXME Check the child is visible. */ if (!child_info->widget) continue; nat_box_size += child_info->nat.height; nat_max_size = MAX (nat_max_size, child_info->nat.height); visible_children++; } if (priv->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_VERTICAL]) nat_box_size = nat_max_size * visible_children; folded = allocation->height < nat_box_size; } hdy_leaflet_set_fold (self, folded ? HDY_FOLD_FOLDED : HDY_FOLD_UNFOLDED); /* Allocate size to the children. */ if (folded) hdy_leaflet_size_allocate_folded (widget, allocation); else hdy_leaflet_size_allocate_unfolded (widget, allocation); /* Apply visibility and allocation. */ for (children = directed_children; children; children = children->next) { child_info = children->data; gtk_widget_set_child_visible (child_info->widget, child_info->visible); if (!child_info->visible) continue; gtk_widget_size_allocate (child_info->widget, &child_info->alloc); if (gtk_widget_get_realized (widget)) gtk_widget_show (child_info->widget); } } static void hdy_leaflet_draw_crossfade (GtkWidget *widget, cairo_t *cr) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gdouble progress = priv->child_transition.progress; cairo_push_group (cr); gtk_container_propagate_draw (GTK_CONTAINER (self), priv->visible_child->widget, cr); cairo_save (cr); /* Multiply alpha by progress */ cairo_set_source_rgba (cr, 1, 1, 1, progress); cairo_set_operator (cr, CAIRO_OPERATOR_DEST_IN); cairo_paint (cr); if (priv->child_transition.last_visible_surface) { cairo_set_source_surface (cr, priv->child_transition.last_visible_surface, priv->child_transition.last_visible_surface_allocation.x, priv->child_transition.last_visible_surface_allocation.y); cairo_set_operator (cr, CAIRO_OPERATOR_ADD); cairo_paint_with_alpha (cr, MAX (1.0 - progress, 0)); } cairo_restore (cr); cairo_pop_group_to_source (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_paint (cr); } static void hdy_leaflet_draw_under (GtkWidget *widget, cairo_t *cr) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkAllocation allocation; int x, y; gtk_widget_get_allocation (widget, &allocation); x = get_bin_window_x (self, &allocation); y = get_bin_window_y (self, &allocation); if (gtk_cairo_should_draw_window (cr, priv->bin_window)) { gint clip_x, clip_y, clip_w, clip_h; gdouble progress; clip_x = 0; clip_y = 0; clip_w = allocation.width; clip_h = allocation.height; switch (priv->child_transition.active_direction) { case GTK_PAN_DIRECTION_LEFT: clip_x = x; clip_w -= x; break; case GTK_PAN_DIRECTION_RIGHT: clip_w += x; break; case GTK_PAN_DIRECTION_UP: clip_y = y; clip_h -= y; break; case GTK_PAN_DIRECTION_DOWN: clip_h += y; break; default: g_assert_not_reached (); break; } progress = priv->child_transition.progress; cairo_save (cr); cairo_rectangle (cr, clip_x, clip_y, clip_w, clip_h); cairo_clip (cr); gtk_container_propagate_draw (GTK_CONTAINER (self), priv->visible_child->widget, cr); cairo_translate (cr, x, y); hdy_shadow_helper_draw_shadow (priv->shadow_helper, cr, allocation.width, allocation.height, progress, priv->child_transition.active_direction); cairo_restore (cr); } if (priv->child_transition.last_visible_surface && gtk_cairo_should_draw_window (cr, priv->view_window)) { switch (priv->child_transition.active_direction) { case GTK_PAN_DIRECTION_LEFT: x -= allocation.width; break; case GTK_PAN_DIRECTION_RIGHT: x += allocation.width; break; case GTK_PAN_DIRECTION_UP: y -= allocation.height; break; case GTK_PAN_DIRECTION_DOWN: y += allocation.height; break; default: g_assert_not_reached (); break; } x += priv->child_transition.last_visible_surface_allocation.x; y += priv->child_transition.last_visible_surface_allocation.y; if (gtk_widget_get_valign (priv->last_visible_child->widget) == GTK_ALIGN_END && priv->child_transition.last_visible_widget_height > allocation.height) y -= priv->child_transition.last_visible_widget_height - allocation.height; else if (gtk_widget_get_valign (priv->last_visible_child->widget) == GTK_ALIGN_CENTER) y -= (priv->child_transition.last_visible_widget_height - allocation.height) / 2; cairo_save (cr); cairo_set_source_surface (cr, priv->child_transition.last_visible_surface, x, y); cairo_paint (cr); cairo_restore (cr); } } static void hdy_leaflet_draw_over (GtkWidget *widget, cairo_t *cr) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); if (priv->child_transition.last_visible_surface && gtk_cairo_should_draw_window (cr, priv->view_window)) { GtkAllocation allocation; gint x, y, clip_x, clip_y, clip_w, clip_h, shadow_x, shadow_y; gdouble progress; GtkPanDirection direction; gtk_widget_get_allocation (widget, &allocation); x = get_bin_window_x (self, &allocation); y = get_bin_window_y (self, &allocation); clip_x = 0; clip_y = 0; clip_w = allocation.width; clip_h = allocation.height; shadow_x = 0; shadow_y = 0; switch (priv->child_transition.active_direction) { case GTK_PAN_DIRECTION_LEFT: shadow_x = x - allocation.width; clip_w = x; x = 0; direction = GTK_PAN_DIRECTION_RIGHT; break; case GTK_PAN_DIRECTION_RIGHT: clip_x = shadow_x = x + allocation.width; clip_w = -x; x = 0; direction = GTK_PAN_DIRECTION_LEFT; break; case GTK_PAN_DIRECTION_UP: shadow_y = y - allocation.height; clip_h = y; y = 0; direction = GTK_PAN_DIRECTION_DOWN; break; case GTK_PAN_DIRECTION_DOWN: clip_y = shadow_y = y + allocation.height; clip_h = -y; y = 0; direction = GTK_PAN_DIRECTION_UP; break; default: g_assert_not_reached (); break; } x += priv->child_transition.last_visible_surface_allocation.x; y += priv->child_transition.last_visible_surface_allocation.y; if (gtk_widget_get_valign (priv->last_visible_child->widget) == GTK_ALIGN_END && priv->child_transition.last_visible_widget_height > allocation.height) y -= priv->child_transition.last_visible_widget_height - allocation.height; else if (gtk_widget_get_valign (priv->last_visible_child->widget) == GTK_ALIGN_CENTER) y -= (priv->child_transition.last_visible_widget_height - allocation.height) / 2; progress = 1 - priv->child_transition.progress; cairo_save (cr); cairo_rectangle (cr, clip_x, clip_y, clip_w, clip_h); cairo_clip (cr); cairo_set_source_surface (cr, priv->child_transition.last_visible_surface, x, y); cairo_paint (cr); cairo_translate (cr, shadow_x, shadow_y); hdy_shadow_helper_draw_shadow (priv->shadow_helper, cr, allocation.width, allocation.height, progress, direction); cairo_restore (cr); } if (gtk_cairo_should_draw_window (cr, priv->bin_window)) gtk_container_propagate_draw (GTK_CONTAINER (self), priv->visible_child->widget, cr); } static void hdy_leaflet_draw_slide (GtkWidget *widget, cairo_t *cr) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); if (priv->child_transition.last_visible_surface && gtk_cairo_should_draw_window (cr, priv->view_window)) { GtkAllocation allocation; int x, y; gtk_widget_get_allocation (widget, &allocation); x = get_bin_window_x (self, &allocation); y = get_bin_window_y (self, &allocation); switch (priv->child_transition.active_direction) { case GTK_PAN_DIRECTION_LEFT: x -= allocation.width; break; case GTK_PAN_DIRECTION_RIGHT: x += allocation.width; break; case GTK_PAN_DIRECTION_UP: y -= allocation.height; break; case GTK_PAN_DIRECTION_DOWN: y += allocation.height; break; default: g_assert_not_reached (); break; } x += priv->child_transition.last_visible_surface_allocation.x; y += priv->child_transition.last_visible_surface_allocation.y; if (gtk_widget_get_valign (priv->last_visible_child->widget) == GTK_ALIGN_END && priv->child_transition.last_visible_widget_height > allocation.height) y -= priv->child_transition.last_visible_widget_height - allocation.height; else if (gtk_widget_get_valign (priv->last_visible_child->widget) == GTK_ALIGN_CENTER) y -= (priv->child_transition.last_visible_widget_height - allocation.height) / 2; cairo_save (cr); cairo_set_source_surface (cr, priv->child_transition.last_visible_surface, x, y); cairo_paint (cr); cairo_restore (cr); } if (gtk_cairo_should_draw_window (cr, priv->bin_window)) gtk_container_propagate_draw (GTK_CONTAINER (self), priv->visible_child->widget, cr); } static void hdy_leaflet_draw_over_or_under (GtkWidget *widget, cairo_t *cr) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gboolean is_rtl; GtkPanDirection direction, left_or_right, right_or_left; direction = priv->child_transition.active_direction; is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; left_or_right = is_rtl ? GTK_PAN_DIRECTION_RIGHT : GTK_PAN_DIRECTION_LEFT; right_or_left = is_rtl ? GTK_PAN_DIRECTION_LEFT : GTK_PAN_DIRECTION_RIGHT; switch (priv->child_transition.active_type) { case HDY_LEAFLET_CHILD_TRANSITION_TYPE_OVER: if (direction == GTK_PAN_DIRECTION_UP || direction == left_or_right) hdy_leaflet_draw_over (widget, cr); else if (direction == GTK_PAN_DIRECTION_DOWN || direction == right_or_left) hdy_leaflet_draw_under (widget, cr); else g_assert_not_reached (); break; case HDY_LEAFLET_CHILD_TRANSITION_TYPE_UNDER: if (direction == GTK_PAN_DIRECTION_UP || direction == left_or_right) hdy_leaflet_draw_under (widget, cr); else if (direction == GTK_PAN_DIRECTION_DOWN || direction == right_or_left) hdy_leaflet_draw_over (widget, cr); else g_assert_not_reached (); break; case HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE: case HDY_LEAFLET_CHILD_TRANSITION_TYPE_CROSSFADE: case HDY_LEAFLET_CHILD_TRANSITION_TYPE_SLIDE: default: g_assert_not_reached (); } } static gboolean hdy_leaflet_draw_unfolded (GtkWidget *widget, cairo_t *cr) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gboolean is_horizontal = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL; GList *directed_children, *children; HdyLeafletChildInfo *child_info; GtkAllocation allocation, child_allocation; directed_children = get_directed_children (self); gtk_widget_get_allocation (widget, &allocation); gtk_widget_get_allocation (priv->visible_child->widget, &child_allocation); cairo_save (cr); cairo_rectangle (cr, 0, 0, is_horizontal ? child_allocation.x : allocation.width, is_horizontal ? allocation.height : child_allocation.y); cairo_clip (cr); for (children = directed_children; children; children = children->next) { child_info = children->data; if (child_info == priv->visible_child) break; gtk_container_propagate_draw (GTK_CONTAINER (self), child_info->widget, cr); } if (priv->mode_transition.start_progress < 1) { gint w, h; w = is_horizontal ? child_allocation.x : allocation.width; h = is_horizontal ? allocation.height : child_allocation.y; if (is_horizontal) w = priv->mode_transition.start_distance; else h = priv->mode_transition.start_distance; cairo_translate (cr, is_horizontal ? child_allocation.x - w : 0, is_horizontal ? 0 : child_allocation.y - h); hdy_shadow_helper_draw_shadow (priv->shadow_helper, cr, w, h, priv->mode_transition.start_progress, is_horizontal ? GTK_PAN_DIRECTION_RIGHT : GTK_PAN_DIRECTION_DOWN); } cairo_restore (cr); gtk_container_propagate_draw (GTK_CONTAINER (self), priv->visible_child->widget, cr); gtk_widget_get_allocation (priv->visible_child->widget, &child_allocation); cairo_save (cr); cairo_rectangle (cr, is_horizontal ? child_allocation.x + child_allocation.width : 0, is_horizontal ? 0 : child_allocation.y + child_allocation.height, is_horizontal ? allocation.width - child_allocation.x - child_allocation.width : allocation.width, is_horizontal ? allocation.height : allocation.height - child_allocation.y - child_allocation.height); cairo_clip (cr); for (children = g_list_last (directed_children); children; children = children->prev) { child_info = children->data; if (child_info == priv->visible_child) break; gtk_container_propagate_draw (GTK_CONTAINER (self), child_info->widget, cr); } if (priv->mode_transition.start_progress < 1) { gint w, h; w = is_horizontal ? child_allocation.x : allocation.width; h = is_horizontal ? allocation.height : child_allocation.y; if (is_horizontal) w = priv->mode_transition.start_distance; else h = priv->mode_transition.start_distance; cairo_translate (cr, is_horizontal ? child_allocation.x - w : 0, is_horizontal ? 0 : child_allocation.y - h); hdy_shadow_helper_draw_shadow (priv->shadow_helper, cr, w, h, priv->mode_transition.start_progress, is_horizontal ? GTK_PAN_DIRECTION_RIGHT : GTK_PAN_DIRECTION_DOWN); } if (priv->mode_transition.end_progress < 1) { gint w, h; w = allocation.width - (is_horizontal ? (child_allocation.x + child_allocation.width) : 0); h = allocation.height - (is_horizontal ? 0 : (child_allocation.y + child_allocation.height)); if (is_horizontal) w = priv->mode_transition.end_distance; else h = priv->mode_transition.end_distance; cairo_translate (cr, is_horizontal ? child_allocation.x + child_allocation.width : 0, is_horizontal ? 0 : child_allocation.y + child_allocation.height); hdy_shadow_helper_draw_shadow (priv->shadow_helper, cr, w, h, priv->mode_transition.end_progress, is_horizontal ? GTK_PAN_DIRECTION_LEFT : GTK_PAN_DIRECTION_UP); } cairo_restore (cr); return FALSE; } static gboolean hdy_leaflet_draw (GtkWidget *widget, cairo_t *cr) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GList *directed_children, *children; HdyLeafletChildInfo *child_info; GtkAllocation allocation; cairo_surface_t *subsurface; cairo_t *pattern_cr; if (priv->fold == HDY_FOLD_UNFOLDED) return hdy_leaflet_draw_unfolded (widget, cr); directed_children = get_directed_children (self); if (gtk_cairo_should_draw_window (cr, priv->view_window)) { GtkStyleContext *context; context = gtk_widget_get_style_context (widget); gtk_render_background (context, cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget)); } if (priv->visible_child) { if (gtk_progress_tracker_get_state (&priv->mode_transition.tracker) != GTK_PROGRESS_STATE_AFTER && priv->fold == HDY_FOLD_FOLDED) { gboolean is_horizontal = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL; if (priv->mode_transition.start_surface == NULL && priv->mode_transition.start_surface_allocation.width != 0 && priv->mode_transition.start_surface_allocation.height != 0) { priv->mode_transition.start_surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), CAIRO_CONTENT_COLOR_ALPHA, priv->mode_transition.start_surface_allocation.width, priv->mode_transition.start_surface_allocation.height); for (children = directed_children; children; children = children->next) { child_info = children->data; if (child_info == priv->visible_child) break; if (!gtk_widget_get_child_visible (child_info->widget)) continue; gtk_widget_get_allocation (child_info->widget, &allocation); subsurface = cairo_surface_create_for_rectangle (priv->mode_transition.start_surface, allocation.x - priv->mode_transition.start_surface_allocation.x, allocation.y - priv->mode_transition.start_surface_allocation.y, allocation.width, allocation.height); pattern_cr = cairo_create (subsurface); gtk_widget_draw (child_info->widget, pattern_cr); cairo_destroy (pattern_cr); cairo_surface_destroy (subsurface); } } if (priv->mode_transition.end_surface == NULL && priv->mode_transition.end_surface_allocation.width != 0 && priv->mode_transition.end_surface_allocation.height != 0) { priv->mode_transition.end_surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), CAIRO_CONTENT_COLOR_ALPHA, priv->mode_transition.end_surface_allocation.width, priv->mode_transition.end_surface_allocation.height); for (children = g_list_last (directed_children); children; children = children->prev) { child_info = children->data; if (child_info == priv->visible_child) break; if (!gtk_widget_get_child_visible (child_info->widget)) continue; gtk_widget_get_allocation (child_info->widget, &allocation); subsurface = cairo_surface_create_for_rectangle (priv->mode_transition.end_surface, allocation.x - priv->mode_transition.end_surface_allocation.x, allocation.y - priv->mode_transition.end_surface_allocation.y, allocation.width, allocation.height); pattern_cr = cairo_create (subsurface); gtk_widget_draw (child_info->widget, pattern_cr); cairo_destroy (pattern_cr); cairo_surface_destroy (subsurface); } } cairo_rectangle (cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget)); cairo_clip (cr); cairo_save (cr); if (priv->mode_transition.start_surface != NULL) { cairo_rectangle (cr, priv->mode_transition.start_surface_allocation.x, priv->mode_transition.start_surface_allocation.y, priv->mode_transition.start_surface_allocation.width, priv->mode_transition.start_surface_allocation.height); cairo_clip (cr); cairo_set_source_surface (cr, priv->mode_transition.start_surface, priv->mode_transition.start_surface_allocation.x, priv->mode_transition.start_surface_allocation.y); cairo_paint (cr); if (priv->mode_transition.start_progress < 1) { gint w, h; w = priv->mode_transition.start_surface_allocation.width; h = priv->mode_transition.start_surface_allocation.height; if (is_horizontal) w = priv->mode_transition.start_distance; else h = priv->mode_transition.start_distance; cairo_translate (cr, priv->mode_transition.start_surface_allocation.width - w, priv->mode_transition.start_surface_allocation.height - h); hdy_shadow_helper_draw_shadow (priv->shadow_helper, cr, w, h, priv->mode_transition.start_progress, is_horizontal ? GTK_PAN_DIRECTION_RIGHT : GTK_PAN_DIRECTION_DOWN); } } cairo_restore (cr); cairo_save (cr); if (priv->mode_transition.end_surface != NULL) { cairo_rectangle (cr, priv->mode_transition.end_surface_clip.x, priv->mode_transition.end_surface_clip.y, priv->mode_transition.end_surface_clip.width, priv->mode_transition.end_surface_clip.height); cairo_clip (cr); cairo_set_source_surface (cr, priv->mode_transition.end_surface, priv->mode_transition.end_surface_allocation.x, priv->mode_transition.end_surface_allocation.y); cairo_paint (cr); if (priv->mode_transition.end_progress < 1) { gint w, h; w = priv->mode_transition.end_surface_allocation.width; h = priv->mode_transition.end_surface_allocation.height; if (is_horizontal) w = priv->mode_transition.end_distance; else h = priv->mode_transition.end_distance; cairo_translate (cr, priv->mode_transition.end_surface_clip.x, priv->mode_transition.end_surface_clip.y); hdy_shadow_helper_draw_shadow (priv->shadow_helper, cr, w, h, priv->mode_transition.end_progress, is_horizontal ? GTK_PAN_DIRECTION_LEFT : GTK_PAN_DIRECTION_UP); } } cairo_restore (cr); if (gtk_cairo_should_draw_window (cr, priv->bin_window)) gtk_container_propagate_draw (GTK_CONTAINER (self), priv->visible_child->widget, cr); } else if ((priv->child_transition.is_gesture_active && get_old_child_transition_type (self) != HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE) || gtk_progress_tracker_get_state (&priv->child_transition.tracker) != GTK_PROGRESS_STATE_AFTER) { if (priv->child_transition.last_visible_surface == NULL && priv->last_visible_child != NULL) { gtk_widget_get_allocation (priv->last_visible_child->widget, &priv->child_transition.last_visible_surface_allocation); priv->child_transition.last_visible_surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), CAIRO_CONTENT_COLOR_ALPHA, priv->child_transition.last_visible_surface_allocation.width, priv->child_transition.last_visible_surface_allocation.height); pattern_cr = cairo_create (priv->child_transition.last_visible_surface); /* We don't use propagate_draw here, because we don't want to apply * the bin_window offset */ { /* FIXME Dirty workaround to get the last visible child to be drawn. * Please fix it properly. * */ gtk_widget_size_allocate (priv->last_visible_child->widget, &priv->child_transition.last_visible_surface_allocation); gtk_widget_set_child_visible (priv->last_visible_child->widget, TRUE); } gtk_widget_draw (priv->last_visible_child->widget, pattern_cr); cairo_destroy (pattern_cr); } cairo_rectangle (cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget)); cairo_clip (cr); switch (priv->child_transition.active_type) { case HDY_LEAFLET_CHILD_TRANSITION_TYPE_CROSSFADE: if (gtk_cairo_should_draw_window (cr, priv->bin_window)) hdy_leaflet_draw_crossfade (widget, cr); break; case HDY_LEAFLET_CHILD_TRANSITION_TYPE_SLIDE: hdy_leaflet_draw_slide (widget, cr); break; case HDY_LEAFLET_CHILD_TRANSITION_TYPE_OVER: case HDY_LEAFLET_CHILD_TRANSITION_TYPE_UNDER: hdy_leaflet_draw_over_or_under (widget, cr); break; case HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE: default: g_assert_not_reached (); } } else if (gtk_cairo_should_draw_window (cr, priv->bin_window)) gtk_container_propagate_draw (GTK_CONTAINER (self), priv->visible_child->widget, cr); } return FALSE; } static void update_tracker_orientation (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gboolean reverse; reverse = (priv->orientation == GTK_ORIENTATION_HORIZONTAL && gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL); g_object_set (priv->tracker, "orientation", priv->orientation, "reversed", reverse, NULL); } static void hdy_leaflet_direction_changed (GtkWidget *widget, GtkTextDirection previous_direction) { HdyLeaflet *self = HDY_LEAFLET (widget); update_tracker_orientation (self); } static void hdy_leaflet_child_visibility_notify_cb (GObject *obj, GParamSpec *pspec, gpointer user_data) { HdyLeaflet *self = HDY_LEAFLET (user_data); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkWidget *widget = GTK_WIDGET (obj); HdyLeafletChildInfo *child_info; child_info = find_child_info_for_widget (self, widget); if (priv->visible_child == NULL && gtk_widget_get_visible (widget)) set_visible_child_info (self, child_info, get_old_child_transition_type (self), priv->child_transition.duration, TRUE); else if (priv->visible_child == child_info && !gtk_widget_get_visible (widget)) set_visible_child_info (self, NULL, get_old_child_transition_type (self), priv->child_transition.duration, TRUE); } static void hdy_leaflet_add (GtkContainer *container, GtkWidget *widget) { HdyLeaflet *self; HdyLeafletPrivate *priv; HdyLeafletChildInfo *child_info; g_return_if_fail (gtk_widget_get_parent (widget) == NULL); self = HDY_LEAFLET (container); priv = hdy_leaflet_get_instance_private (self); gtk_widget_set_child_visible (widget, FALSE); gtk_widget_set_parent_window (widget, priv->bin_window); gtk_widget_set_parent (widget, GTK_WIDGET (self)); child_info = g_new0 (HdyLeafletChildInfo, 1); child_info->widget = widget; child_info->allow_visible = TRUE; priv->children = g_list_append (priv->children, child_info); priv->children_reversed = g_list_prepend (priv->children_reversed, child_info); if (priv->bin_window) gdk_window_set_events (priv->bin_window, gdk_window_get_events (priv->bin_window) | gtk_widget_get_events (widget)); g_signal_connect (widget, "notify::visible", G_CALLBACK (hdy_leaflet_child_visibility_notify_cb), self); if (hdy_leaflet_get_visible_child (self) == NULL && gtk_widget_get_visible (widget)) { set_visible_child_info (self, child_info, get_old_child_transition_type (self), priv->child_transition.duration, FALSE); } if (priv->fold == HDY_FOLD_UNFOLDED || (priv->fold == HDY_FOLD_FOLDED && (priv->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_HORIZONTAL] || priv->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_VERTICAL] || priv->visible_child == child_info))) gtk_widget_queue_resize (GTK_WIDGET (self)); } static void hdy_leaflet_remove (GtkContainer *container, GtkWidget *widget) { HdyLeaflet *self = HDY_LEAFLET (container); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); HdyLeafletChildInfo *child_info; gboolean contains_child; child_info = find_child_info_for_widget (self, widget); contains_child = child_info != NULL; g_return_if_fail (contains_child); priv->children = g_list_remove (priv->children, child_info); priv->children_reversed = g_list_remove (priv->children_reversed, child_info); free_child_info (child_info); if (hdy_leaflet_get_visible_child (self) == widget) set_visible_child_info (self, NULL, get_old_child_transition_type (self), priv->child_transition.duration, TRUE); if (gtk_widget_get_visible (widget)) gtk_widget_queue_resize (GTK_WIDGET (container)); gtk_widget_unparent (widget); } static void hdy_leaflet_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { HdyLeaflet *self = HDY_LEAFLET (container); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); /* This shallow copy is needed when the callback changes the list while we are * looping through it, for example by calling hdy_leaflet_remove() on all * children when destroying the HdyLeaflet_private_offset. */ g_autoptr (GList) children_copy = g_list_copy (priv->children); GList *children; HdyLeafletChildInfo *child_info; for (children = children_copy; children; children = children->next) { child_info = children->data; (* callback) (child_info->widget, callback_data); } g_list_free (priv->children_reversed); priv->children_reversed = g_list_copy (priv->children); priv->children_reversed = g_list_reverse (priv->children_reversed); } static void hdy_leaflet_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyLeaflet *self = HDY_LEAFLET (object); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); switch (prop_id) { case PROP_FOLD: g_value_set_enum (value, hdy_leaflet_get_fold (self)); break; case PROP_FOLDED: g_value_set_boolean (value, hdy_leaflet_get_fold (self) == HDY_FOLD_FOLDED); break; case PROP_HHOMOGENEOUS_FOLDED: g_value_set_boolean (value, hdy_leaflet_get_homogeneous (self, TRUE, GTK_ORIENTATION_HORIZONTAL)); break; case PROP_VHOMOGENEOUS_FOLDED: g_value_set_boolean (value, hdy_leaflet_get_homogeneous (self, TRUE, GTK_ORIENTATION_VERTICAL)); break; case PROP_HHOMOGENEOUS_UNFOLDED: g_value_set_boolean (value, hdy_leaflet_get_homogeneous (self, FALSE, GTK_ORIENTATION_HORIZONTAL)); break; case PROP_VHOMOGENEOUS_UNFOLDED: g_value_set_boolean (value, hdy_leaflet_get_homogeneous (self, FALSE, GTK_ORIENTATION_VERTICAL)); break; case PROP_VISIBLE_CHILD: g_value_set_object (value, hdy_leaflet_get_visible_child (self)); break; case PROP_VISIBLE_CHILD_NAME: g_value_set_string (value, hdy_leaflet_get_visible_child_name (self)); break; case PROP_TRANSITION_TYPE: g_value_set_enum (value, hdy_leaflet_get_transition_type (self)); break; case PROP_MODE_TRANSITION_TYPE: g_value_set_enum (value, hdy_leaflet_get_mode_transition_type (self)); break; case PROP_MODE_TRANSITION_DURATION: g_value_set_uint (value, hdy_leaflet_get_mode_transition_duration (self)); break; case PROP_CHILD_TRANSITION_TYPE: g_value_set_enum (value, hdy_leaflet_get_child_transition_type (self)); break; case PROP_CHILD_TRANSITION_DURATION: g_value_set_uint (value, hdy_leaflet_get_child_transition_duration (self)); break; case PROP_CHILD_TRANSITION_RUNNING: g_value_set_boolean (value, hdy_leaflet_get_child_transition_running (self)); break; case PROP_INTERPOLATE_SIZE: g_value_set_boolean (value, hdy_leaflet_get_interpolate_size (self)); break; case PROP_CAN_SWIPE_BACK: g_value_set_boolean (value, hdy_leaflet_get_can_swipe_back (self)); break; case PROP_CAN_SWIPE_FORWARD: g_value_set_boolean (value, hdy_leaflet_get_can_swipe_forward (self)); break; case PROP_ORIENTATION: g_value_set_enum (value, priv->orientation); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_leaflet_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyLeaflet *self = HDY_LEAFLET (object); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); switch (prop_id) { case PROP_HHOMOGENEOUS_FOLDED: hdy_leaflet_set_homogeneous (self, TRUE, GTK_ORIENTATION_HORIZONTAL, g_value_get_boolean (value)); break; case PROP_VHOMOGENEOUS_FOLDED: hdy_leaflet_set_homogeneous (self, TRUE, GTK_ORIENTATION_VERTICAL, g_value_get_boolean (value)); break; case PROP_HHOMOGENEOUS_UNFOLDED: hdy_leaflet_set_homogeneous (self, FALSE, GTK_ORIENTATION_HORIZONTAL, g_value_get_boolean (value)); break; case PROP_VHOMOGENEOUS_UNFOLDED: hdy_leaflet_set_homogeneous (self, FALSE, GTK_ORIENTATION_VERTICAL, g_value_get_boolean (value)); break; case PROP_VISIBLE_CHILD: hdy_leaflet_set_visible_child (self, g_value_get_object (value)); break; case PROP_VISIBLE_CHILD_NAME: hdy_leaflet_set_visible_child_name (self, g_value_get_string (value)); break; case PROP_TRANSITION_TYPE: hdy_leaflet_set_transition_type (self, g_value_get_enum (value)); break; case PROP_MODE_TRANSITION_TYPE: hdy_leaflet_set_mode_transition_type (self, g_value_get_enum (value)); break; case PROP_MODE_TRANSITION_DURATION: hdy_leaflet_set_mode_transition_duration (self, g_value_get_uint (value)); break; case PROP_CHILD_TRANSITION_TYPE: hdy_leaflet_set_child_transition_type (self, g_value_get_enum (value)); break; case PROP_CHILD_TRANSITION_DURATION: hdy_leaflet_set_child_transition_duration (self, g_value_get_uint (value)); break; case PROP_INTERPOLATE_SIZE: hdy_leaflet_set_interpolate_size (self, g_value_get_boolean (value)); break; case PROP_CAN_SWIPE_BACK: hdy_leaflet_set_can_swipe_back (self, g_value_get_boolean (value)); break; case PROP_CAN_SWIPE_FORWARD: hdy_leaflet_set_can_swipe_forward (self, g_value_get_boolean (value)); break; case PROP_ORIENTATION: { GtkOrientation orientation = g_value_get_enum (value); if (priv->orientation != orientation) { priv->orientation = orientation; update_tracker_orientation (self); gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify (object, "orientation"); } } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_leaflet_dispose (GObject *object) { HdyLeaflet *self = HDY_LEAFLET (object); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); priv->visible_child = NULL; if (priv->shadow_helper) g_clear_object (&priv->shadow_helper); G_OBJECT_CLASS (hdy_leaflet_parent_class)->dispose (object); } static void hdy_leaflet_finalize (GObject *object) { HdyLeaflet *self = HDY_LEAFLET (object); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); hdy_leaflet_unschedule_child_ticks (self); if (priv->child_transition.last_visible_surface != NULL) cairo_surface_destroy (priv->child_transition.last_visible_surface); g_object_set_data (object, "captured-event-handler", NULL); G_OBJECT_CLASS (hdy_leaflet_parent_class)->finalize (object); } static void hdy_leaflet_get_child_property (GtkContainer *container, GtkWidget *widget, guint property_id, GValue *value, GParamSpec *pspec) { HdyLeaflet *self = HDY_LEAFLET (container); HdyLeafletChildInfo *child_info; child_info = find_child_info_for_widget (self, widget); if (child_info == NULL) { GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); return; } switch (property_id) { case CHILD_PROP_NAME: g_value_set_string (value, child_info->name); break; case CHILD_PROP_ALLOW_VISIBLE: g_value_set_boolean (value, child_info->allow_visible); break; default: GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); break; } } static void hdy_leaflet_set_child_property (GtkContainer *container, GtkWidget *widget, guint property_id, const GValue *value, GParamSpec *pspec) { HdyLeaflet *self = HDY_LEAFLET (container); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); HdyLeafletChildInfo *child_info; HdyLeafletChildInfo *child_info2; gchar *name; GList *children; child_info = find_child_info_for_widget (self, widget); if (child_info == NULL) { GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); return; } switch (property_id) { case CHILD_PROP_NAME: name = g_value_dup_string (value); for (children = priv->children; children; children = children->next) { child_info2 = children->data; if (child_info == child_info2) continue; if (g_strcmp0 (child_info2->name, name) == 0) { g_warning ("Duplicate child name in HdyLeaflet: %s", name); break; } } g_free (child_info->name); child_info->name = name; gtk_container_child_notify_by_pspec (container, widget, pspec); if (priv->visible_child == child_info) g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD_NAME]); break; case CHILD_PROP_ALLOW_VISIBLE: child_info->allow_visible = g_value_get_boolean (value); gtk_container_child_notify_by_pspec (container, widget, pspec); if (!child_info->allow_visible && hdy_leaflet_get_visible_child (self) == widget) set_visible_child_info (self, NULL, get_old_child_transition_type (self), priv->child_transition.duration, TRUE); break; default: GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); break; } } static void hdy_leaflet_realize (GtkWidget *widget) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GtkAllocation allocation; GdkWindowAttr attributes = { 0 }; GdkWindowAttributesType attributes_mask; GList *children; HdyLeafletChildInfo *child_info; GtkBorder padding; gtk_widget_set_realized (widget, TRUE); gtk_widget_set_window (widget, g_object_ref (gtk_widget_get_parent_window (widget))); gtk_widget_get_allocation (widget, &allocation); attributes.x = allocation.x; attributes.y = allocation.y; attributes.width = allocation.width; attributes.height = allocation.height; attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.event_mask = gtk_widget_get_events (widget); attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL; priv->view_window = gdk_window_new (gtk_widget_get_window (widget), &attributes, attributes_mask); gtk_widget_register_window (widget, priv->view_window); get_padding (widget, &padding); attributes.x = padding.left; attributes.y = padding.top; attributes.width = allocation.width; attributes.height = allocation.height; for (children = priv->children; children != NULL; children = children->next) { child_info = children->data; attributes.event_mask |= gtk_widget_get_events (child_info->widget); } priv->bin_window = gdk_window_new (priv->view_window, &attributes, attributes_mask); gtk_widget_register_window (widget, priv->bin_window); for (children = priv->children; children != NULL; children = children->next) { child_info = children->data; gtk_widget_set_parent_window (child_info->widget, priv->bin_window); } gdk_window_show (priv->bin_window); } static void hdy_leaflet_unrealize (GtkWidget *widget) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gtk_widget_unregister_window (widget, priv->bin_window); gdk_window_destroy (priv->bin_window); priv->bin_window = NULL; gtk_widget_unregister_window (widget, priv->view_window); gdk_window_destroy (priv->view_window); priv->view_window = NULL; GTK_WIDGET_CLASS (hdy_leaflet_parent_class)->unrealize (widget); } static void hdy_leaflet_map (GtkWidget *widget) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); GTK_WIDGET_CLASS (hdy_leaflet_parent_class)->map (widget); gdk_window_show (priv->view_window); } static void hdy_leaflet_unmap (GtkWidget *widget) { HdyLeaflet *self = HDY_LEAFLET (widget); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gdk_window_hide (priv->view_window); GTK_WIDGET_CLASS (hdy_leaflet_parent_class)->unmap (widget); } static void hdy_leaflet_switch_child (HdySwipeable *swipeable, guint index, gint64 duration) { HdyLeaflet *self = HDY_LEAFLET (swipeable); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); HdyLeafletChildInfo *child_info; child_info = g_list_nth_data (priv->children, index); set_visible_child_info (self, child_info, get_old_child_transition_type (self), duration, FALSE); } static HdyLeafletChildInfo * find_swipeable_child (HdyLeaflet *self, gint direction) { HdyLeafletPrivate *priv; GList *children; HdyLeafletChildInfo *child = NULL; priv = hdy_leaflet_get_instance_private (self); children = g_list_find (priv->children, priv->visible_child); do { children = (direction < 0) ? children->prev : children->next; if (children == NULL) break; child = children->data; } while (child && !child->allow_visible); return child; } static double get_current_progress (HdyLeaflet *self) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gboolean new_first = FALSE; GList *children; if (!priv->child_transition.is_gesture_active && gtk_progress_tracker_get_state (&priv->child_transition.tracker) == GTK_PROGRESS_STATE_AFTER) return 0; for (children = priv->children; children; children = children->next) { if (priv->last_visible_child == children->data) { new_first = TRUE; break; } if (priv->visible_child == children->data) break; } return priv->child_transition.progress * (new_first ? 1 : -1); } static void hdy_leaflet_begin_swipe (HdySwipeable *swipeable, gint direction, gboolean direct) { HdyLeaflet *self = HDY_LEAFLET (swipeable); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); gint n; gdouble *points, distance, progress; if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) distance = gtk_widget_get_allocated_width (GTK_WIDGET (self)); else distance = gtk_widget_get_allocated_height (GTK_WIDGET (self)); if (priv->child_transition.tick_id > 0) { gint current_direction; gboolean is_rtl; is_rtl = (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL); switch (priv->child_transition.active_direction) { case GTK_PAN_DIRECTION_UP: current_direction = 1; break; case GTK_PAN_DIRECTION_DOWN: current_direction = -1; break; case GTK_PAN_DIRECTION_LEFT: current_direction = is_rtl ? -1 : 1; break; case GTK_PAN_DIRECTION_RIGHT: current_direction = is_rtl ? 1 : -1; break; default: g_assert_not_reached (); } n = 2; points = g_new0 (gdouble, n); points[current_direction > 0 ? 1 : 0] = current_direction; progress = get_current_progress (self); gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->child_transition.tick_id); priv->child_transition.tick_id = 0; priv->child_transition.is_gesture_active = TRUE; priv->child_transition.is_cancelled = FALSE; } else { HdyLeafletChildInfo *child; if (((direction < 0 && priv->child_transition.can_swipe_back) || (direction > 0 && priv->child_transition.can_swipe_forward) || !direct) && priv->fold == HDY_FOLD_FOLDED) child = find_swipeable_child (self, direction); else child = NULL; if (child) { priv->child_transition.is_gesture_active = TRUE; set_visible_child_info (self, child, get_old_child_transition_type (self), priv->child_transition.duration, FALSE); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TRANSITION_RUNNING]); } progress = 0; n = child ? 2 : 1; points = g_new0 (gdouble, n); if (child) points[direction > 0 ? 1 : 0] = direction; } hdy_swipe_tracker_confirm_swipe (priv->tracker, distance, points, n, progress, 0); } static void hdy_leaflet_update_swipe (HdySwipeable *swipeable, gdouble value) { HdyLeaflet *self = HDY_LEAFLET (swipeable); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); priv->child_transition.progress = ABS (value); hdy_leaflet_child_progress_updated (self); } static void hdy_leaflet_end_swipe (HdySwipeable *swipeable, gint64 duration, gdouble to) { HdyLeaflet *self = HDY_LEAFLET (swipeable); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); if (!priv->child_transition.is_gesture_active) return; priv->child_transition.start_progress = priv->child_transition.progress; priv->child_transition.end_progress = ABS (to); priv->child_transition.is_cancelled = (to == 0); priv->child_transition.first_frame_skipped = TRUE; hdy_leaflet_schedule_child_ticks (self); if (hdy_get_enable_animations (GTK_WIDGET (self)) && duration != 0 && get_old_child_transition_type (self) != HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE) { gtk_progress_tracker_start (&priv->child_transition.tracker, duration * 1000, 0, 1.0); } else { priv->child_transition.progress = priv->child_transition.end_progress; gtk_progress_tracker_finish (&priv->child_transition.tracker); } priv->child_transition.is_gesture_active = FALSE; hdy_leaflet_child_progress_updated (self); gtk_widget_queue_draw (GTK_WIDGET (self)); } static gboolean captured_event_cb (HdyLeaflet *self, GdkEvent *event) { HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); return hdy_swipe_tracker_captured_event (priv->tracker, event); } static void hdy_leaflet_class_init (HdyLeafletClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = (GtkWidgetClass*) klass; GtkContainerClass *container_class = (GtkContainerClass*) klass; object_class->get_property = hdy_leaflet_get_property; object_class->set_property = hdy_leaflet_set_property; object_class->dispose = hdy_leaflet_dispose; object_class->finalize = hdy_leaflet_finalize; widget_class->realize = hdy_leaflet_realize; widget_class->unrealize = hdy_leaflet_unrealize; widget_class->map = hdy_leaflet_map; widget_class->unmap = hdy_leaflet_unmap; widget_class->get_preferred_width = hdy_leaflet_get_preferred_width; widget_class->get_preferred_height = hdy_leaflet_get_preferred_height; widget_class->get_preferred_width_for_height = hdy_leaflet_get_preferred_width_for_height; widget_class->get_preferred_height_for_width = hdy_leaflet_get_preferred_height_for_width; widget_class->size_allocate = hdy_leaflet_size_allocate; widget_class->draw = hdy_leaflet_draw; widget_class->direction_changed = hdy_leaflet_direction_changed; container_class->add = hdy_leaflet_add; container_class->remove = hdy_leaflet_remove; container_class->forall = hdy_leaflet_forall; container_class->set_child_property = hdy_leaflet_set_child_property; container_class->get_child_property = hdy_leaflet_get_child_property; gtk_container_class_handle_border_width (container_class); g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); /** * HdyLeaflet:fold: * * The fold of the leaflet. * * The leaflet will be folded if the size allocated to it is smaller than the * sum of the natural size of its children, it will be unfolded otherwise. * * See also: #HdyLeaflet:folded. */ props[PROP_FOLD] = g_param_spec_enum ("fold", _("Fold"), _("Whether the widget is folded"), HDY_TYPE_FOLD, HDY_FOLD_UNFOLDED, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyLeaflet:folded: * * %TRUE if the leaflet is folded. * * This is similar to the #HdyLeaflet:fold property but expressed as a * #gboolean rather than a #GEnum. This makes it convenient to bind the * #HdyLeaflet:fold of a leaflet to any other #gboolean property of other * #GObject's using #g_object_bind_property(). */ props[PROP_FOLDED] = g_param_spec_boolean ("folded", _("Folded"), _("Whether the widget is folded"), FALSE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyLeaflet:hhomogeneous_folded: * * %TRUE if the leaflet allocates the same width for all children when folded. */ props[PROP_HHOMOGENEOUS_FOLDED] = g_param_spec_boolean ("hhomogeneous-folded", _("Horizontally homogeneous folded"), _("Horizontally homogeneous sizing when the leaflet is folded"), TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyLeaflet:vhomogeneous_folded: * * %TRUE if the leaflet allocates the same height for all children when folded. */ props[PROP_VHOMOGENEOUS_FOLDED] = g_param_spec_boolean ("vhomogeneous-folded", _("Vertically homogeneous folded"), _("Vertically homogeneous sizing when the leaflet is folded"), TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyLeaflet:hhomogeneous_unfolded: * * %TRUE if the leaflet allocates the same width for all children when unfolded. */ props[PROP_HHOMOGENEOUS_UNFOLDED] = g_param_spec_boolean ("hhomogeneous-unfolded", _("Box horizontally homogeneous"), _("Horizontally homogeneous sizing when the leaflet is unfolded"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyLeaflet:vhomogeneous_unfolded: * * %TRUE if the leaflet allocates the same height for all children when unfolded. */ props[PROP_VHOMOGENEOUS_UNFOLDED] = g_param_spec_boolean ("vhomogeneous-unfolded", _("Box vertically homogeneous"), _("Vertically homogeneous sizing when the leaflet is unfolded"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_VISIBLE_CHILD] = g_param_spec_object ("visible-child", _("Visible child"), _("The widget currently visible when the leaflet is folded"), GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_VISIBLE_CHILD_NAME] = g_param_spec_string ("visible-child-name", _("Name of visible child"), _("The name of the widget currently visible when the children are stacked"), NULL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyLeaflet:transition-type: * * The type of animation that will be used for transitions between modes and * children. * * The transition type can be changed without problems at runtime, so it is * possible to change the animation based on the mode or child that is about * to become current. * * Since: 0.0.12 */ props[PROP_TRANSITION_TYPE] = g_param_spec_enum ("transition-type", _("Transition type"), _("The type of animation used to transition between modes and children"), HDY_TYPE_LEAFLET_TRANSITION_TYPE, HDY_LEAFLET_TRANSITION_TYPE_NONE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyLeaflet:mode-transition-type: * * The type of animation used to transition between mode * * Deprecated: 0.0.12: Use #HdyLeaflet:transition-type instead */ props[PROP_MODE_TRANSITION_TYPE] = g_param_spec_enum ("mode-transition-type", _("Mode transition type"), _("The type of animation used to transition between modes"), HDY_TYPE_LEAFLET_MODE_TRANSITION_TYPE, HDY_LEAFLET_MODE_TRANSITION_TYPE_NONE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_DEPRECATED); props[PROP_MODE_TRANSITION_DURATION] = g_param_spec_uint ("mode-transition-duration", _("Mode transition duration"), _("The mode transition animation duration, in milliseconds"), 0, G_MAXUINT, 250, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyLeaflet:child-transition-type: * * The type of animation used to transition between children * * Deprecated: 0.0.12: Use #HdyLeaflet:transition-type instead */ props[PROP_CHILD_TRANSITION_TYPE] = g_param_spec_enum ("child-transition-type", _("Child transition type"), _("The type of animation used to transition between children"), HDY_TYPE_LEAFLET_CHILD_TRANSITION_TYPE, HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_DEPRECATED); props[PROP_CHILD_TRANSITION_DURATION] = g_param_spec_uint ("child-transition-duration", _("Child transition duration"), _("The child transition animation duration, in milliseconds"), 0, G_MAXUINT, 200, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_CHILD_TRANSITION_RUNNING] = g_param_spec_boolean ("child-transition-running", _("Child transition running"), _("Whether or not the child transition is currently running"), FALSE, G_PARAM_READABLE); props[PROP_INTERPOLATE_SIZE] = g_param_spec_boolean ("interpolate-size", _("Interpolate size"), _("Whether or not the size should smoothly change when changing between differently sized children"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyLeaflet:can-swipe-back: * * Whether or not @self allows switching to the previous child that has * 'allow-visible' child property set to %TRUE via a swipe gesture. * * Since: 0.0.12 */ props[PROP_CAN_SWIPE_BACK] = g_param_spec_boolean ("can-swipe-back", _("Can swipe back"), _("Whether or not swipe gesture can be used to switch to the previous child"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyLeaflet:can-swipe-forward: * * Whether or not @self allows switching to the next child that has * 'allow-visible' child property set to %TRUE via a swipe gesture. * * Since: 0.0.12 */ props[PROP_CAN_SWIPE_FORWARD] = g_param_spec_boolean ("can-swipe-forward", _("Can swipe forward"), _("Whether or not swipe gesture can be used to switch to the next child"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); child_props[CHILD_PROP_NAME] = g_param_spec_string ("name", _("Name"), _("The name of the child page"), NULL, G_PARAM_READWRITE); /** * HdyLeaflet:allow-visible: * * Whether the child can be visible when folded. This can be used used in * conjunction with #HdyLeaflet:can-swipe-back or * #HdyLeaflet:can-swipe-forward to prevent switching to widgets like * separators. * * Since: 0.0.12 */ child_props[CHILD_PROP_ALLOW_VISIBLE] = g_param_spec_boolean ("allow-visible", _("Allow visible"), _("Whether the child can be visible in folded mode"), TRUE, G_PARAM_READWRITE); gtk_container_class_install_child_properties (container_class, LAST_CHILD_PROP, child_props); gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_PANEL); gtk_widget_class_set_css_name (widget_class, "hdyleaflet"); } GtkWidget * hdy_leaflet_new (void) { return g_object_new (HDY_TYPE_LEAFLET, NULL); } static void hdy_leaflet_init (HdyLeaflet *self) { GtkWidget *widget = GTK_WIDGET (self); HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); priv->children = NULL; priv->children_reversed = NULL; priv->visible_child = NULL; priv->fold = HDY_FOLD_UNFOLDED; priv->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_HORIZONTAL] = FALSE; priv->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_VERTICAL] = FALSE; priv->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_HORIZONTAL] = TRUE; priv->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_VERTICAL] = TRUE; priv->transition_type = HDY_LEAFLET_TRANSITION_TYPE_NONE; priv->mode_transition.type = HDY_LEAFLET_MODE_TRANSITION_TYPE_NONE; priv->mode_transition.duration = 250; priv->child_transition.type = HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE; priv->child_transition.duration = 200; priv->mode_transition.current_pos = 1.0; priv->mode_transition.target_pos = 1.0; priv->tracker = hdy_swipe_tracker_new (HDY_SWIPEABLE (self)); g_object_set (priv->tracker, "orientation", priv->orientation, "enabled", FALSE, NULL); priv->shadow_helper = hdy_shadow_helper_new (widget, "/sm/puri/handy/style/hdy-leaflet.css"); gtk_widget_set_has_window (widget, FALSE); gtk_widget_set_can_focus (widget, FALSE); gtk_widget_set_redraw_on_allocate (widget, FALSE); /* * HACK: GTK3 has no other way to get events on capture phase. * This is a reimplementation of _gtk_widget_set_captured_event_handler(), * which is private. In GTK4 it can be replaced with GtkEventControllerLegacy * with capture propagation phase */ g_object_set_data (G_OBJECT (self), "captured-event-handler", captured_event_cb); } static void hdy_leaflet_buildable_init (GtkBuildableIface *iface) { } static void hdy_leaflet_swipeable_init (HdySwipeableInterface *iface) { iface->switch_child = hdy_leaflet_switch_child; iface->begin_swipe = hdy_leaflet_begin_swipe; iface->update_swipe = hdy_leaflet_update_swipe; iface->end_swipe = hdy_leaflet_end_swipe; } libhandy-0.0.13/src/hdy-leaflet.css000066400000000000000000000031651360136463700171020ustar00rootroot00000000000000hdyleaflet > dimming { background: rgba(0, 0, 0, 0.12); } hdyleaflet > shadow { min-width: 56px; min-height: 56px; } hdyleaflet > border { min-width: 1px; min-height: 1px; background: rgba(0, 0, 0, 0.05); } hdyleaflet > shadow.left { background-image: linear-gradient(to right, alpha(#000, 0.05), alpha(#000, 0.01) 40px, alpha(#000, 0) 56px), linear-gradient(to right, alpha(#000, 0.03), alpha(#000, 0.01) 7px, alpha(#000, 0) 24px); } hdyleaflet > shadow.right { background-image: linear-gradient(to left, alpha(#000, 0.05), alpha(#000, 0.01) 40px, alpha(#000, 0) 56px), linear-gradient(to left, alpha(#000, 0.03), alpha(#000, 0.01) 7px, alpha(#000, 0) 24px); } hdyleaflet > shadow.up { background-image: linear-gradient(to bottom, alpha(#000, 0.05), alpha(#000, 0.01) 40px, alpha(#000, 0) 56px), linear-gradient(to bottom, alpha(#000, 0.03), alpha(#000, 0.01) 7px, alpha(#000, 0) 24px); } hdyleaflet > shadow.down { background-image: linear-gradient(to top, alpha(#000, 0.05), alpha(#000, 0.01) 40px, alpha(#000, 0) 56px), linear-gradient(to top, alpha(#000, 0.03), alpha(#000, 0.01) 7px, alpha(#000, 0) 24px); } libhandy-0.0.13/src/hdy-leaflet.h000066400000000000000000000112121360136463700165310ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include "hdy-deprecation-macros.h" #include "hdy-fold.h" #include "hdy-enums.h" G_BEGIN_DECLS #define HDY_TYPE_LEAFLET (hdy_leaflet_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyLeaflet, hdy_leaflet, HDY, LEAFLET, GtkContainer) typedef enum { HDY_LEAFLET_TRANSITION_TYPE_NONE, HDY_LEAFLET_TRANSITION_TYPE_SLIDE, HDY_LEAFLET_TRANSITION_TYPE_OVER, HDY_LEAFLET_TRANSITION_TYPE_UNDER, } HdyLeafletTransitionType; _HDY_DEPRECATED_FOR (HdyLeafletTransitionType) typedef enum { HDY_LEAFLET_MODE_TRANSITION_TYPE_NONE, HDY_LEAFLET_MODE_TRANSITION_TYPE_SLIDE, } HdyLeafletModeTransitionType; _HDY_DEPRECATED_FOR (HdyLeafletTransitionType) typedef enum { HDY_LEAFLET_CHILD_TRANSITION_TYPE_NONE, HDY_LEAFLET_CHILD_TRANSITION_TYPE_CROSSFADE, HDY_LEAFLET_CHILD_TRANSITION_TYPE_SLIDE, HDY_LEAFLET_CHILD_TRANSITION_TYPE_OVER, HDY_LEAFLET_CHILD_TRANSITION_TYPE_UNDER, } HdyLeafletChildTransitionType; /** * HdyLeafletClass * @parent_class: The parent class */ struct _HdyLeafletClass { GtkContainerClass parent_class; /*< private >*/ /* Signals */ void (*todo) (HdyLeaflet *self); }; GtkWidget *hdy_leaflet_new (void); HdyFold hdy_leaflet_get_fold (HdyLeaflet *self); GtkWidget *hdy_leaflet_get_visible_child (HdyLeaflet *self); void hdy_leaflet_set_visible_child (HdyLeaflet *self, GtkWidget *visible_child); const gchar *hdy_leaflet_get_visible_child_name (HdyLeaflet *self); void hdy_leaflet_set_visible_child_name (HdyLeaflet *self, const gchar *name); gboolean hdy_leaflet_get_homogeneous (HdyLeaflet *self, HdyFold fold, GtkOrientation orientation); void hdy_leaflet_set_homogeneous (HdyLeaflet *self, HdyFold fold, GtkOrientation orientation, gboolean homogeneous); HdyLeafletTransitionType hdy_leaflet_get_transition_type (HdyLeaflet *self); void hdy_leaflet_set_transition_type (HdyLeaflet *self, HdyLeafletTransitionType transition); G_GNUC_BEGIN_IGNORE_DEPRECATIONS _HDY_DEPRECATED_FOR (hdy_leaflet_get_transition_type) HdyLeafletModeTransitionType hdy_leaflet_get_mode_transition_type (HdyLeaflet *self); _HDY_DEPRECATED_FOR (hdy_leaflet_set_transition_type) void hdy_leaflet_set_mode_transition_type (HdyLeaflet *self, HdyLeafletModeTransitionType transition); G_GNUC_END_IGNORE_DEPRECATIONS guint hdy_leaflet_get_mode_transition_duration (HdyLeaflet *self); void hdy_leaflet_set_mode_transition_duration (HdyLeaflet *self, guint duration); G_GNUC_BEGIN_IGNORE_DEPRECATIONS _HDY_DEPRECATED_FOR (hdy_leaflet_get_transition_type) HdyLeafletChildTransitionType hdy_leaflet_get_child_transition_type (HdyLeaflet *self); _HDY_DEPRECATED_FOR (hdy_leaflet_set_transition_type) void hdy_leaflet_set_child_transition_type (HdyLeaflet *self, HdyLeafletChildTransitionType transition); G_GNUC_END_IGNORE_DEPRECATIONS guint hdy_leaflet_get_child_transition_duration (HdyLeaflet *self); void hdy_leaflet_set_child_transition_duration (HdyLeaflet *self, guint duration); gboolean hdy_leaflet_get_child_transition_running (HdyLeaflet *self); gboolean hdy_leaflet_get_interpolate_size (HdyLeaflet *self); void hdy_leaflet_set_interpolate_size (HdyLeaflet *self, gboolean interpolate_size); gboolean hdy_leaflet_get_can_swipe_back (HdyLeaflet *self); void hdy_leaflet_set_can_swipe_back (HdyLeaflet *self, gboolean can_swipe_back); gboolean hdy_leaflet_get_can_swipe_forward (HdyLeaflet *self); void hdy_leaflet_set_can_swipe_forward (HdyLeaflet *self, gboolean can_swipe_forward); G_END_DECLS libhandy-0.0.13/src/hdy-list-box.c000066400000000000000000000023661360136463700166630ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "hdy-list-box.h" #include /** * SECTION:hdy-list-box * @short_description: Helper functions for #GtkListBox. * @Title: GtkListBox helpers * * Since: 0.0.6 */ /** * hdy_list_box_separator_header: * @row: the row to update * @before: (allow-none): the row before @row, or %NULL if it is first * @unused_user_data: (closure): unused user data * * Separates rows by using #GtkSeparator as headers. The first row doesn't have * a separator as there is no row above it. * * Since: 0.0.6 */ void hdy_list_box_separator_header (GtkListBoxRow *row, GtkListBoxRow *before, gpointer unused_user_data) { GtkWidget *header; g_return_if_fail (GTK_IS_LIST_BOX_ROW (row)); g_return_if_fail (before == NULL || GTK_IS_LIST_BOX_ROW (before)); /* No header for the first row. */ if (before == NULL) { gtk_list_box_row_set_header (row, NULL); return; } if (gtk_list_box_row_get_header (row) != NULL) return; header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); gtk_widget_show (header); gtk_list_box_row_set_header (row, header); } libhandy-0.0.13/src/hdy-list-box.h000066400000000000000000000006661360136463700166710ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS void hdy_list_box_separator_header (GtkListBoxRow *row, GtkListBoxRow *before, gpointer unused_user_data); G_END_DECLS libhandy-0.0.13/src/hdy-main-private.h000066400000000000000000000010701360136463700175120ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include "hdy-main.h" G_BEGIN_DECLS /* Initializes the public GObject types, which is needed to ensure they are * discoverable, for example so they can easily be used with GtkBuilder. * * The function is implemented in hdy-public-types.c which is generated at * compile time by gen-public-types.sh */ void hdy_init_public_types (void); G_END_DECLS libhandy-0.0.13/src/hdy-main.c000066400000000000000000000027641360136463700160500ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "hdy-main-private.h" #include #include static gint hdy_initialized = FALSE; /** * SECTION:hdy-main * @short_description: Library initialization. * @Title: hdy-main * * Before using the Handy libarary you should initialize it. This makes * sure translations for the Handy library are set up properly. */ GResource *hdy_get_resource (void); /** * hdy_init: * @argc: (inout) (optional): Address of the argc * parameter of your main() function (or 0 if @argv is %NULL). This will be * changed if any arguments were handled. * @argv: (array length=argc) (inout) (nullable) (optional) (transfer none): * Address of the argv parameter of main(), or %NULL. * Any options understood by Handy are stripped before return. * * Call this function before using any other Handy functions in your * GUI applications. If libhandy has already been initialized, the function will * simply return without processing the new arguments. * * Returns: %TRUE if initialization was successful, %FALSE otherwise. */ gboolean hdy_init (int *argc, char ***argv) { if (hdy_initialized) return TRUE; bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); g_resources_register (hdy_get_resource ()); hdy_init_public_types (); hdy_initialized = TRUE; return TRUE; } libhandy-0.0.13/src/hdy-main.h000066400000000000000000000004571360136463700160520ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS gboolean hdy_init(int *argc, char ***argv); G_END_DECLS libhandy-0.0.13/src/hdy-paginator-box-private.h000066400000000000000000000042521360136463700213450ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_PAGINATOR_BOX (hdy_paginator_box_get_type()) G_DECLARE_FINAL_TYPE (HdyPaginatorBox, hdy_paginator_box, HDY, PAGINATOR_BOX, GtkContainer) HdyPaginatorBox *hdy_paginator_box_new (void); void hdy_paginator_box_insert (HdyPaginatorBox *self, GtkWidget *child, gint position); void hdy_paginator_box_reorder (HdyPaginatorBox *self, GtkWidget *child, gint position); void hdy_paginator_box_animate (HdyPaginatorBox *self, gdouble position, gint64 duration); gboolean hdy_paginator_box_is_animating (HdyPaginatorBox *self); void hdy_paginator_box_stop_animation (HdyPaginatorBox *self); void hdy_paginator_box_scroll_to (HdyPaginatorBox *self, GtkWidget *widget, gint64 duration); guint hdy_paginator_box_get_n_pages (HdyPaginatorBox *self); gdouble hdy_paginator_box_get_distance (HdyPaginatorBox *self); gdouble hdy_paginator_box_get_position (HdyPaginatorBox *self); void hdy_paginator_box_set_position (HdyPaginatorBox *self, gdouble position); guint hdy_paginator_box_get_spacing (HdyPaginatorBox *self); void hdy_paginator_box_set_spacing (HdyPaginatorBox *self, guint spacing); GtkWidget *hdy_paginator_box_get_nth_child (HdyPaginatorBox *self, guint n); G_END_DECLS libhandy-0.0.13/src/hdy-paginator-box.c000066400000000000000000000770301360136463700176740ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-paginator-box-private.h" #include "hdy-animation-private.h" #include /** * PRIVATE:hdy-paginator-box * @short_description: Scrolling box used in #HdyPaginator * @title: HdyPaginatorBox * @See_also: #HdyPaginator * @stability: Private * * The #HdyPaginatorBox object is meant to be used exclusively as part of * the #HdyPaginator implementation. * * Since: 0.0.11 */ typedef struct _HdyPaginatorBoxChildInfo HdyPaginatorBoxChildInfo; struct _HdyPaginatorBoxChildInfo { GtkWidget *widget; GdkWindow *window; gint position; gboolean visible; cairo_surface_t *surface; cairo_region_t *dirty_region; }; struct _HdyPaginatorBox { GtkContainer parent_instance; struct { guint tick_cb_id; gint64 start_time; gint64 end_time; gdouble start_position; gdouble end_position; } animation_data; GList *children; gint child_width; gint child_height; gdouble distance; gdouble position; guint spacing; GtkOrientation orientation; }; G_DEFINE_TYPE_WITH_CODE (HdyPaginatorBox, hdy_paginator_box, GTK_TYPE_CONTAINER, G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)); enum { PROP_0, PROP_N_PAGES, PROP_POSITION, PROP_SPACING, /* GtkOrientable */ PROP_ORIENTATION, LAST_PROP = PROP_SPACING + 1, }; static GParamSpec *props[LAST_PROP]; enum { SIGNAL_ANIMATION_STOPPED, SIGNAL_LAST_SIGNAL, }; static guint signals[SIGNAL_LAST_SIGNAL]; static HdyPaginatorBoxChildInfo * find_child_info (HdyPaginatorBox *self, GtkWidget *widget) { GList *l; for (l = self->children; l; l = l->next) { HdyPaginatorBoxChildInfo *info = l->data; if (widget == info->widget) return info; } return NULL; } static gint find_child_index (HdyPaginatorBox *self, GtkWidget *widget) { GList *l; gint i; i = 0; for (l = self->children; l; l = l->next) { HdyPaginatorBoxChildInfo *info = l->data; if (widget == info->widget) return i; i++; } return -1; } static HdyPaginatorBoxChildInfo * find_child_info_by_window (HdyPaginatorBox *self, GdkWindow *window) { GList *l; for (l = self->children; l; l = l->next) { HdyPaginatorBoxChildInfo *info = l->data; if (window == info->window) return info; } return NULL; } static void free_child_info (HdyPaginatorBoxChildInfo *info) { if (info->surface) cairo_surface_destroy (info->surface); if (info->dirty_region) cairo_region_destroy (info->dirty_region); g_free (info); } static void invalidate_handler_cb (GdkWindow *window, cairo_region_t *region) { gpointer user_data; HdyPaginatorBox *self; HdyPaginatorBoxChildInfo *info; gdk_window_get_user_data (window, &user_data); g_assert (HDY_IS_PAGINATOR_BOX (user_data)); self = HDY_PAGINATOR_BOX (user_data); info = find_child_info_by_window (self, window); if (!info->dirty_region) info->dirty_region = cairo_region_create (); cairo_region_union (info->dirty_region, region); } static void register_window (HdyPaginatorBoxChildInfo *info, HdyPaginatorBox *self) { GtkWidget *widget; GdkWindow *window; GdkWindowAttr attributes; GtkAllocation allocation; gint attributes_mask; widget = GTK_WIDGET (self); gtk_widget_get_allocation (info->widget, &allocation); attributes.x = allocation.x; attributes.y = allocation.y; attributes.width = allocation.width; attributes.height = allocation.height; attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.event_mask = gtk_widget_get_events (widget); attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL; window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gtk_widget_register_window (widget, window); gtk_widget_set_parent_window (info->widget, window); gdk_window_set_user_data (window, self); gdk_window_show (window); info->window = window; gdk_window_set_invalidate_handler (window, invalidate_handler_cb); } static void unregister_window (HdyPaginatorBoxChildInfo *info, HdyPaginatorBox *self) { gtk_widget_set_parent_window (info->widget, NULL); gtk_widget_unregister_window (GTK_WIDGET (self), info->window); gdk_window_destroy (info->window); info->window = NULL; } static gboolean animation_cb (GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (widget); gint64 frame_time, duration; gdouble position; gdouble t; g_assert (hdy_paginator_box_is_animating (self)); frame_time = gdk_frame_clock_get_frame_time (frame_clock) / 1000; frame_time = MIN (frame_time, self->animation_data.end_time); duration = self->animation_data.end_time - self->animation_data.start_time; position = (gdouble) (frame_time - self->animation_data.start_time) / duration; t = hdy_ease_out_cubic (position); hdy_paginator_box_set_position (self, hdy_lerp (self->animation_data.start_position, self->animation_data.end_position, 1 - t)); if (frame_time == self->animation_data.end_time) { self->animation_data.tick_cb_id = 0; g_signal_emit (self, signals[SIGNAL_ANIMATION_STOPPED], 0); return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } static gboolean hdy_paginator_box_draw (GtkWidget *widget, cairo_t *cr) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (widget); GList *l; for (l = self->children; l; l = l->next) { HdyPaginatorBoxChildInfo *info = l->data; if (!info->visible) continue; if (info->dirty_region) { cairo_t *surface_cr; GtkAllocation child_alloc; if (!info->surface) { gint width, height; width = gdk_window_get_width (info->window); height = gdk_window_get_height (info->window); info->surface = gdk_window_create_similar_surface (info->window, CAIRO_CONTENT_COLOR_ALPHA, width, height); } gtk_widget_get_allocation (info->widget, &child_alloc); surface_cr = cairo_create (info->surface); gdk_cairo_region (surface_cr, info->dirty_region); cairo_clip (surface_cr); if (self->orientation == GTK_ORIENTATION_VERTICAL) cairo_translate (surface_cr, 0, -info->position); else cairo_translate (surface_cr, -info->position, 0); cairo_save (surface_cr); cairo_set_source_rgba (surface_cr, 0, 0, 0, 0); cairo_set_operator (surface_cr, CAIRO_OPERATOR_SOURCE); cairo_paint (surface_cr); cairo_restore (surface_cr); gtk_container_propagate_draw (GTK_CONTAINER (self), info->widget, surface_cr); cairo_destroy (surface_cr); cairo_region_destroy (info->dirty_region); info->dirty_region = NULL; } if (!info->surface) continue; if (self->orientation == GTK_ORIENTATION_VERTICAL) cairo_set_source_surface (cr, info->surface, 0, info->position); else cairo_set_source_surface (cr, info->surface, info->position, 0); cairo_paint (cr); } return GDK_EVENT_PROPAGATE; } static void measure (GtkWidget *widget, GtkOrientation orientation, gint for_size, gint *minimum, gint *natural, gint *minimum_baseline, gint *natural_baseline) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (widget); GList *children; if (minimum) *minimum = 0; if (natural) *natural = 0; if (minimum_baseline) *minimum_baseline = -1; if (natural_baseline) *natural_baseline = -1; for (children = self->children; children; children = children->next) { HdyPaginatorBoxChildInfo *child_info = children->data; GtkWidget *child = child_info->widget; gint child_min, child_nat; if (!gtk_widget_get_visible (child)) continue; if (orientation == GTK_ORIENTATION_VERTICAL) { if (for_size < 0) gtk_widget_get_preferred_height (child, &child_min, &child_nat); else gtk_widget_get_preferred_height_for_width (child, for_size, &child_min, &child_nat); } else { if (for_size < 0) gtk_widget_get_preferred_width (child, &child_min, &child_nat); else gtk_widget_get_preferred_width_for_height (child, for_size, &child_min, &child_nat); } if (minimum) *minimum = MAX (*minimum, child_min); if (natural) *natural = MAX (*natural, child_nat); } } static void hdy_paginator_box_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width) { measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, minimum_width, natural_width, NULL, NULL); } static void hdy_paginator_box_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height) { measure (widget, GTK_ORIENTATION_VERTICAL, -1, minimum_height, natural_height, NULL, NULL); } static void hdy_paginator_box_get_preferred_width_for_height (GtkWidget *widget, gint for_height, gint *minimum_width, gint *natural_width) { measure (widget, GTK_ORIENTATION_HORIZONTAL, for_height, minimum_width, natural_width, NULL, NULL); } static void hdy_paginator_box_get_preferred_height_for_width (GtkWidget *widget, gint for_width, gint *minimum_height, gint *natural_height) { measure (widget, GTK_ORIENTATION_VERTICAL, for_width, minimum_height, natural_height, NULL, NULL); } static void invalidate_cache_for_child (HdyPaginatorBox *self, HdyPaginatorBoxChildInfo *child) { cairo_rectangle_int_t rect; rect.x = 0; rect.y = 0; rect.width = self->child_width; rect.height = self->child_height; if (child->surface) g_clear_pointer (&child->surface, cairo_surface_destroy); if (child->dirty_region) cairo_region_destroy (child->dirty_region); child->dirty_region = cairo_region_create_rectangle (&rect); } static void invalidate_drawing_cache (HdyPaginatorBox *self) { GList *l; for (l = self->children; l; l = l->next) { HdyPaginatorBoxChildInfo *child_info = l->data; invalidate_cache_for_child (self, child_info); } } static void update_windows (HdyPaginatorBox *self) { GList *children; GtkAllocation alloc; gint x, y, offset; gboolean is_rtl; if (!gtk_widget_get_realized (GTK_WIDGET (self))) return; gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); x = alloc.x; y = alloc.y; is_rtl = (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL); if (self->orientation == GTK_ORIENTATION_VERTICAL) offset = (self->distance * self->position) - (alloc.height - self->child_height) / 2.0; else if (is_rtl) offset = -(self->distance * self->position) + (alloc.width - self->child_width) / 2.0; else offset = (self->distance * self->position) - (alloc.width - self->child_width) / 2.0; if (self->orientation == GTK_ORIENTATION_VERTICAL) y -= offset; else x -= offset; for (children = self->children; children; children = children->next) { HdyPaginatorBoxChildInfo *child_info = children->data; if (!gtk_widget_get_visible (child_info->widget)) continue; if (self->orientation == GTK_ORIENTATION_VERTICAL) { child_info->position = y; child_info->visible = child_info->position < alloc.height && child_info->position + self->child_height > 0; gdk_window_move (child_info->window, alloc.x, alloc.y + child_info->position); } else { child_info->position = x; child_info->visible = child_info->position < alloc.width && child_info->position + self->child_width > 0; gdk_window_move (child_info->window, alloc.x + child_info->position, alloc.y); } if (!child_info->visible) invalidate_cache_for_child (self, child_info); if (self->orientation == GTK_ORIENTATION_VERTICAL) y += self->distance; else if (is_rtl) x -= self->distance; else x += self->distance; } } static void hdy_paginator_box_map (GtkWidget *widget) { GTK_WIDGET_CLASS (hdy_paginator_box_parent_class)->map (widget); gtk_widget_queue_draw (GTK_WIDGET (widget)); } static void hdy_paginator_box_realize (GtkWidget *widget) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (widget); GTK_WIDGET_CLASS (hdy_paginator_box_parent_class)->realize (widget); g_list_foreach (self->children, (GFunc) register_window, self); gtk_widget_queue_allocate (widget); } static void hdy_paginator_box_unrealize (GtkWidget *widget) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (widget); g_list_foreach (self->children, (GFunc) unregister_window, self); GTK_WIDGET_CLASS (hdy_paginator_box_parent_class)->unrealize (widget); } static void hdy_paginator_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (widget); gint size, width, height; GList *children; gtk_widget_set_allocation (widget, allocation); size = 0; for (children = self->children; children; children = children->next) { HdyPaginatorBoxChildInfo *child_info = children->data; GtkWidget *child = child_info->widget; gint min, nat; gint child_size; if (self->orientation == GTK_ORIENTATION_HORIZONTAL) { gtk_widget_get_preferred_width_for_height (child, allocation->height, &min, &nat); if (gtk_widget_get_hexpand (child)) child_size = MAX (min, allocation->width); else child_size = MAX (min, nat); } else { gtk_widget_get_preferred_height_for_width (child, allocation->width, &min, &nat); if (gtk_widget_get_vexpand (child)) child_size = MAX (min, allocation->height); else child_size = MAX (min, nat); } size = MAX (size, child_size); } self->distance = size + self->spacing; if (self->orientation == GTK_ORIENTATION_HORIZONTAL) { width = size; height = allocation->height; } else { width = allocation->width; height = size; } if (width != self->child_width || height != self->child_height) invalidate_drawing_cache (self); self->child_width = width; self->child_height = height; for (children = self->children; children; children = children->next) { HdyPaginatorBoxChildInfo *child_info = children->data; if (!gtk_widget_get_visible (child_info->widget)) continue; if (!gtk_widget_get_realized (GTK_WIDGET (self))) continue; gdk_window_resize (child_info->window, width, height); } update_windows (self); for (children = self->children; children; children = children->next) { HdyPaginatorBoxChildInfo *child_info = children->data; GtkWidget *child = child_info->widget; GtkAllocation alloc; if (!gtk_widget_get_visible (child)) continue; alloc.x = 0; alloc.y = 0; alloc.width = width; alloc.height = height; gtk_widget_size_allocate (child, &alloc); } invalidate_drawing_cache (self); gtk_widget_set_clip (widget, allocation); } static void hdy_paginator_box_add (GtkContainer *container, GtkWidget *widget) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (container); HdyPaginatorBoxChildInfo *info; info = g_new0 (HdyPaginatorBoxChildInfo, 1); info->widget = widget; if (gtk_widget_get_realized (GTK_WIDGET (container))) register_window (info, self); self->children = g_list_append (self->children, info); gtk_widget_set_parent (widget, GTK_WIDGET (container)); invalidate_drawing_cache (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_N_PAGES]); } static void hdy_paginator_box_remove (GtkContainer *container, GtkWidget *widget) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (container); gint index; HdyPaginatorBoxChildInfo *info; info = find_child_info (self, widget); if (!info) return; gtk_widget_unparent (widget); index = g_list_index (self->children, info); self->children = g_list_remove (self->children, info); if (gtk_widget_get_realized (GTK_WIDGET (container))) unregister_window (info, self); free_child_info (info); if (self->position >= index) hdy_paginator_box_set_position (self, self->position - 1); else gtk_widget_queue_allocate (GTK_WIDGET (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_N_PAGES]); } static void hdy_paginator_box_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (container); GList *list; GtkWidget *child; list = self->children; while (list) { HdyPaginatorBoxChildInfo *child_info = list->data; child = child_info->widget; list = list->next; (* callback) (child, callback_data); } } static void hdy_paginator_box_finalize (GObject *object) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (object); hdy_paginator_box_stop_animation (self); g_list_free_full (self->children, (GDestroyNotify) free_child_info); G_OBJECT_CLASS (hdy_paginator_box_parent_class)->finalize (object); } static void hdy_paginator_box_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (object); switch (prop_id) { case PROP_N_PAGES: g_value_set_uint (value, hdy_paginator_box_get_n_pages (self)); break; case PROP_POSITION: g_value_set_double (value, hdy_paginator_box_get_position (self)); break; case PROP_SPACING: g_value_set_uint (value, hdy_paginator_box_get_spacing (self)); break; case PROP_ORIENTATION: g_value_set_enum (value, self->orientation); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_paginator_box_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyPaginatorBox *self = HDY_PAGINATOR_BOX (object); switch (prop_id) { case PROP_POSITION: hdy_paginator_box_set_position (self, g_value_get_double (value)); break; case PROP_SPACING: hdy_paginator_box_set_spacing (self, g_value_get_uint (value)); break; case PROP_ORIENTATION: { GtkOrientation orientation = g_value_get_enum (value); if (orientation != self->orientation) { self->orientation = orientation; gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify (G_OBJECT (self), "orientation"); } } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_paginator_box_class_init (HdyPaginatorBoxClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->finalize = hdy_paginator_box_finalize; object_class->get_property = hdy_paginator_box_get_property; object_class->set_property = hdy_paginator_box_set_property; widget_class->draw = hdy_paginator_box_draw; widget_class->get_preferred_width = hdy_paginator_box_get_preferred_width; widget_class->get_preferred_height = hdy_paginator_box_get_preferred_height; widget_class->get_preferred_width_for_height = hdy_paginator_box_get_preferred_width_for_height; widget_class->get_preferred_height_for_width = hdy_paginator_box_get_preferred_height_for_width; widget_class->map = hdy_paginator_box_map; widget_class->realize = hdy_paginator_box_realize; widget_class->unrealize = hdy_paginator_box_unrealize; widget_class->size_allocate = hdy_paginator_box_size_allocate; container_class->add = hdy_paginator_box_add; container_class->remove = hdy_paginator_box_remove; container_class->forall = hdy_paginator_box_forall; /** * HdyPaginatorBox:n-pages: * * The number of pages in a #HdyPaginatorBox * * Since: 0.0.11 */ props[PROP_N_PAGES] = g_param_spec_uint ("n-pages", _("Number of pages"), _("Number of pages"), 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPaginatorBox:position: * * Current scrolling position, unitless. 1 matches 1 page. * * Since: 0.0.11 */ props[PROP_POSITION] = g_param_spec_double ("position", _("Position"), _("Current scrolling position"), 0, G_MAXDOUBLE, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPaginatorBox:spacing: * * Spacing between pages in pixels. * * Since: 0.0.11 */ props[PROP_SPACING] = g_param_spec_uint ("spacing", _("Spacing"), _("Spacing between pages"), 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); g_object_class_install_properties (object_class, LAST_PROP, props); /** * HdyPaginatorBox::animation-stopped: * @self: The #HdyPaginatorBox instance * * This signal is emitted after an animation has been stopped. If animations * are disabled, the signal is emitted as well. * * Since: 0.0.12 */ signals[SIGNAL_ANIMATION_STOPPED] = g_signal_new ("animation-stopped", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void hdy_paginator_box_init (HdyPaginatorBox *self) { GtkWidget *widget = GTK_WIDGET (self); self->orientation = GTK_ORIENTATION_HORIZONTAL; gtk_widget_set_has_window (widget, FALSE); } /** * hdy_paginator_box_new: * * Create a new #HdyPaginatorBox widget. * * Returns: The newly created #HdyPaginatorBox widget * * Since: 0.0.11 */ HdyPaginatorBox * hdy_paginator_box_new (void) { return g_object_new (HDY_TYPE_PAGINATOR_BOX, NULL); } /** * hdy_paginator_box_insert: * @self: a #HdyPaginatorBox * @child: a widget to add * @position: the position to insert @child in. * * Inserts @child into @self at position @position. * * If position is -1, or larger than the number of pages, @child will be * appended to the end. * * Since: 0.0.11 */ void hdy_paginator_box_insert (HdyPaginatorBox *self, GtkWidget *child, gint position) { g_return_if_fail (HDY_IS_PAGINATOR_BOX (self)); g_return_if_fail (GTK_IS_WIDGET (child)); gtk_container_add (GTK_CONTAINER (self), child); hdy_paginator_box_reorder (self, child, position); } /** * hdy_paginator_box_reorder: * @self: a #HdyPaginatorBox * @child: a widget to add * @position: the position to move @child to. * * Moves @child into position @position. * * If position is -1, or larger than the number of pages, @child will be moved * to the end. * * Since: 0.0.11 */ void hdy_paginator_box_reorder (HdyPaginatorBox *self, GtkWidget *child, gint position) { HdyPaginatorBoxChildInfo *info; GList *link; gint old_position, current_page; g_return_if_fail (HDY_IS_PAGINATOR_BOX (self)); g_return_if_fail (GTK_IS_WIDGET (child)); info = find_child_info (self, child); link = g_list_find (self->children, info); old_position = g_list_position (self->children, link); self->children = g_list_delete_link (self->children, link); if (position < 0 || position >= hdy_paginator_box_get_n_pages (self)) link = NULL; else link = g_list_nth (self->children, position); self->children = g_list_insert_before (self->children, link, info); current_page = round (self->position); if (current_page == old_position) hdy_paginator_box_set_position (self, position); else if (old_position > current_page && position <= current_page) hdy_paginator_box_set_position (self, self->position + 1); else if (old_position <= current_page && position > current_page) hdy_paginator_box_set_position (self, self->position - 1); } /** * hdy_paginator_box_animate: * @self: a #HdyPaginatorBox * @position: A value to animate to * @duration: Animation duration in milliseconds * * Animates the widget's position to @position over the next @duration * milliseconds using easeOutCubic interpolator. * * If an animation was already running, it will be cancelled automatically. * * @duration can be 0, in that case the position will be * changed immediately. * * Since: 0.0.11 */ void hdy_paginator_box_animate (HdyPaginatorBox *self, gdouble position, gint64 duration) { GdkFrameClock *frame_clock; gint64 frame_time; g_return_if_fail (HDY_IS_PAGINATOR_BOX (self)); hdy_paginator_box_stop_animation (self); if (duration <= 0 || !hdy_get_enable_animations (GTK_WIDGET (self))) { hdy_paginator_box_set_position (self, position); g_signal_emit (self, signals[SIGNAL_ANIMATION_STOPPED], 0); return; } frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (self)); if (!frame_clock) { hdy_paginator_box_set_position (self, position); g_signal_emit (self, signals[SIGNAL_ANIMATION_STOPPED], 0); return; } frame_time = gdk_frame_clock_get_frame_time (frame_clock); self->animation_data.start_position = self->position; self->animation_data.end_position = position; self->animation_data.start_time = frame_time / 1000; self->animation_data.end_time = self->animation_data.start_time + duration; self->animation_data.tick_cb_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), animation_cb, self, NULL); } /** * hdy_paginator_box_is_animating: * @self: a #HdyPaginatorBox * * Get whether @self is animating position. * * Returns: %TRUE if an animation is running * * Since: 0.0.11 */ gboolean hdy_paginator_box_is_animating (HdyPaginatorBox *self) { g_return_val_if_fail (HDY_IS_PAGINATOR_BOX (self), FALSE); return (self->animation_data.tick_cb_id != 0); } /** * hdy_paginator_box_stop_animation: * @self: a #HdyPaginatorBox * * Stops a running animation. If there's no animation running, does nothing. * * It does not reset position to a non-transient value automatically. * * Since: 0.0.11 */ void hdy_paginator_box_stop_animation (HdyPaginatorBox *self) { g_return_if_fail (HDY_IS_PAGINATOR_BOX (self)); if (self->animation_data.tick_cb_id == 0) return; gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->animation_data.tick_cb_id); self->animation_data.tick_cb_id = 0; } /** * hdy_paginator_box_scroll_to: * @self: a #HdyPaginatorBox * @widget: a child of @self * @duration: animation duration in milliseconds * * Scrolls to @widget position with an animation. If @duration is 0, changes * the position immediately. * * Since: 0.0.11 */ void hdy_paginator_box_scroll_to (HdyPaginatorBox *self, GtkWidget *widget, gint64 duration) { gint index; g_return_if_fail (HDY_IS_PAGINATOR_BOX (self)); g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (duration >= 0); index = find_child_index (self, widget); hdy_paginator_box_animate (self, index, duration); } /** * hdy_paginator_box_get_n_pages: * @self: a #HdyPaginatorBox * * Gets the number of pages in @self. * * Returns: The number of pages in @self * * Since: 0.0.11 */ guint hdy_paginator_box_get_n_pages (HdyPaginatorBox *self) { g_return_val_if_fail (HDY_IS_PAGINATOR_BOX (self), 0); return g_list_length (self->children); } /** * hdy_paginator_box_get_distance: * @self: a #HdyPaginatorBox * * Gets swiping distance between two adjacent children in pixels. * * Returns: The swiping distance in pixels * * Since: 0.0.11 */ gdouble hdy_paginator_box_get_distance (HdyPaginatorBox *self) { g_return_val_if_fail (HDY_IS_PAGINATOR_BOX (self), 0); return self->distance; } /** * hdy_paginator_box_get_position: * @self: a #HdyPaginatorBox * * Gets current scroll position in @self. It's unitless, 1 matches 1 page. * * Returns: The scroll position * * Since: 0.0.11 */ gdouble hdy_paginator_box_get_position (HdyPaginatorBox *self) { g_return_val_if_fail (HDY_IS_PAGINATOR_BOX (self), 0); return self->position; } /** * hdy_paginator_box_set_position: * @self: a #HdyPaginatorBox * @position: the new position value * * Sets current scroll position in @self, unitless, 1 matches 1 page. * * Since: 0.0.11 */ void hdy_paginator_box_set_position (HdyPaginatorBox *self, gdouble position) { g_return_if_fail (HDY_IS_PAGINATOR_BOX (self)); position = CLAMP (position, 0, hdy_paginator_box_get_n_pages (self) - 1); self->position = position; update_windows (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POSITION]); } /** * hdy_paginator_box_get_spacing: * @self: a #HdyPaginatorBox * * Gets spacing between pages in pixels. * * Returns: Spacing between pages * * Since: 0.0.11 */ guint hdy_paginator_box_get_spacing (HdyPaginatorBox *self) { g_return_val_if_fail (HDY_IS_PAGINATOR_BOX (self), 0); return self->spacing; } /** * hdy_paginator_box_set_spacing: * @self: a #HdyPaginatorBox * @spacing: the new spacing value * * Sets spacing between pages in pixels. * * Since: 0.0.11 */ void hdy_paginator_box_set_spacing (HdyPaginatorBox *self, guint spacing) { g_return_if_fail (HDY_IS_PAGINATOR_BOX (self)); if (self->spacing == spacing) return; self->spacing = spacing; gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SPACING]); } /** * hdy_paginator_box_get_nth_child: * @self: a #HdyPaginatorBox * @n: the child index * * Retrieves @n-th child widget of @self. * * Returns: The @n-th child widget * * Since: 0.0.12 */ GtkWidget * hdy_paginator_box_get_nth_child (HdyPaginatorBox *self, guint n) { HdyPaginatorBoxChildInfo *info; g_return_val_if_fail (HDY_IS_PAGINATOR_BOX (self), NULL); g_return_val_if_fail (n < g_list_length (self->children), NULL); info = g_list_nth_data (self->children, n); return info->widget; } libhandy-0.0.13/src/hdy-paginator.c000066400000000000000000001144521360136463700171060ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-paginator.h" #include "hdy-paginator-box-private.h" #include "hdy-swipe-tracker-private.h" #include "hdy-swipeable-private.h" #include #define DOTS_RADIUS 3 #define DOTS_RADIUS_SELECTED 4 #define DOTS_OPACITY 0.3 #define DOTS_OPACITY_SELECTED 0.9 #define DOTS_SPACING 7 #define DOTS_MARGIN 6 #define LINE_WIDTH 3 #define LINE_LENGTH 35 #define LINE_SPACING 5 #define LINE_OPACITY 0.3 #define LINE_OPACITY_ACTIVE 0.9 #define LINE_MARGIN 2 #define DEFAULT_DURATION 250 /** * SECTION:hdy-paginator * @short_description: A paginated scrolling widget. * @title: HdyPaginator * * The #HdyPaginator widget can be used to display a set of pages with * swipe-based navigation between them and optional indicators. * * Since: 0.0.11 */ /** * HdyPaginatorIndicatorStyle * @HDY_PAGINATOR_INDICATOR_STYLE_NONE: No indicators * @HDY_PAGINATOR_INDICATOR_STYLE_DOTS: Each page is represented by a dot. Active dot gradually becomes larger and more opaque. * @HDY_PAGINATOR_INDICATOR_STYLE_LINES: Each page is represented by a thin and long line, and active view is shown with another line that moves between them * * These enumeration values describe the possible page indicator styles in a * #HdyPaginator widget. * * New values may be added to this enumeration over time. */ struct _HdyPaginator { GtkEventBox parent_instance; GtkBox *box; GtkBox *empty_box; HdyPaginatorBox *scrolling_box; GtkDrawingArea *indicators; HdySwipeTracker *tracker; HdyPaginatorIndicatorStyle indicator_style; guint indicator_spacing; gboolean center_content; GtkOrientation orientation; guint animation_duration; gulong scroll_timeout_id; gboolean can_scroll; }; static void hdy_paginator_swipeable_init (HdySwipeableInterface *iface); G_DEFINE_TYPE_WITH_CODE (HdyPaginator, hdy_paginator, GTK_TYPE_EVENT_BOX, G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL) G_IMPLEMENT_INTERFACE (HDY_TYPE_SWIPEABLE, hdy_paginator_swipeable_init)) enum { PROP_0, PROP_N_PAGES, PROP_POSITION, PROP_INTERACTIVE, PROP_INDICATOR_STYLE, PROP_INDICATOR_SPACING, PROP_CENTER_CONTENT, PROP_SPACING, PROP_ANIMATION_DURATION, PROP_ALLOW_MOUSE_DRAG, /* GtkOrientable */ PROP_ORIENTATION, LAST_PROP = PROP_ALLOW_MOUSE_DRAG + 1, }; static GParamSpec *props[LAST_PROP]; enum { SIGNAL_PAGE_CHANGED, SIGNAL_LAST_SIGNAL, }; static guint signals[SIGNAL_LAST_SIGNAL]; static void hdy_paginator_switch_child (HdySwipeable *swipeable, guint index, gint64 duration) { HdyPaginator *self = HDY_PAGINATOR (swipeable); GtkWidget *child; child = hdy_paginator_box_get_nth_child (self->scrolling_box, index); hdy_paginator_box_scroll_to (self->scrolling_box, child, duration); } static void hdy_paginator_begin_swipe (HdySwipeable *swipeable, gint direction, gboolean direct) { HdyPaginator *self = HDY_PAGINATOR (swipeable); gdouble distance, position, closest_point; guint i, n_pages; gdouble *points; hdy_paginator_box_stop_animation (self->scrolling_box); distance = hdy_paginator_box_get_distance (self->scrolling_box); g_object_get (self->scrolling_box, "position", &position, "n-pages", &n_pages, NULL); closest_point = CLAMP (round (position), 0, n_pages - 1); points = g_new (gdouble, n_pages); for (i = 0; i < n_pages; i++) points[i] = i; hdy_swipe_tracker_confirm_swipe (self->tracker, distance, points, n_pages, position, closest_point); } static void hdy_paginator_update_swipe (HdySwipeable *swipeable, gdouble value) { HdyPaginator *self = HDY_PAGINATOR (swipeable); hdy_paginator_box_set_position (self->scrolling_box, value); } static void hdy_paginator_end_swipe (HdySwipeable *swipeable, gint64 duration, gdouble to) { HdyPaginator *self = HDY_PAGINATOR (swipeable); if (duration == 0) { hdy_paginator_box_set_position (self->scrolling_box, to); return; } hdy_paginator_box_animate (self->scrolling_box, to, duration); } static void notify_n_pages_cb (HdyPaginator *self, GParamSpec *spec, GObject *object) { g_object_notify_by_pspec (G_OBJECT (self), props[PROP_N_PAGES]); gtk_widget_queue_draw (GTK_WIDGET (self->indicators)); } static void notify_position_cb (HdyPaginator *self, GParamSpec *spec, GObject *object) { g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POSITION]); gtk_widget_queue_draw (GTK_WIDGET (self->indicators)); } static void notify_spacing_cb (HdyPaginator *self, GParamSpec *spec, GObject *object) { g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SPACING]); } static void animation_stopped_cb (HdyPaginator *self, HdyPaginatorBox *box) { gdouble position; gint index; position = hdy_paginator_box_get_position (self->scrolling_box); index = round (position); g_signal_emit (self, signals[SIGNAL_PAGE_CHANGED], 0, index); } static GdkRGBA get_color (GtkWidget *widget) { GtkStyleContext *context; GtkStateFlags flags; GdkRGBA color; context = gtk_widget_get_style_context (widget); flags = gtk_widget_get_state_flags (widget); gtk_style_context_get_color (context, flags, &color); return color; } static void draw_indicators_lines (GtkWidget *widget, cairo_t *cr, GtkOrientation orientation, gdouble position, guint n_pages) { GdkRGBA color; gint i, widget_length, indicator_length; gdouble length; color = get_color (widget); length = (gdouble) LINE_LENGTH / (LINE_LENGTH + LINE_SPACING); indicator_length = (LINE_LENGTH + LINE_SPACING) * n_pages - LINE_SPACING; if (orientation == GTK_ORIENTATION_HORIZONTAL) { widget_length = gtk_widget_get_allocated_width (widget); cairo_translate (cr, (widget_length - indicator_length) / 2, 0); cairo_scale (cr, LINE_LENGTH + LINE_SPACING, LINE_WIDTH); } else { widget_length = gtk_widget_get_allocated_height (widget); cairo_translate (cr, 0, (widget_length - indicator_length) / 2); cairo_scale (cr, LINE_WIDTH, LINE_LENGTH + LINE_SPACING); } cairo_set_source_rgba (cr, color.red, color.green, color.blue, color.alpha * LINE_OPACITY); for (i = 0; i < n_pages; i++) { if (orientation == GTK_ORIENTATION_HORIZONTAL) cairo_rectangle (cr, i, 0, length, 1); else cairo_rectangle (cr, 0, i, 1, length); cairo_fill (cr); } cairo_set_source_rgba (cr, color.red, color.green, color.blue, color.alpha * LINE_OPACITY_ACTIVE); if (orientation == GTK_ORIENTATION_HORIZONTAL) cairo_rectangle (cr, position, 0, length, 1); else cairo_rectangle (cr, 0, position, 1, length); cairo_fill (cr); } #define LERP(a, b, t) ((a) + (((b) - (a)) * (t))) static void draw_indicators_dots (GtkWidget *widget, cairo_t *cr, GtkOrientation orientation, gdouble position, guint n_pages) { GdkRGBA color; gint i, x, y, widget_length, indicator_length; color = get_color (widget); indicator_length = (DOTS_RADIUS_SELECTED * 2 + DOTS_SPACING) * n_pages - DOTS_SPACING; if (orientation == GTK_ORIENTATION_HORIZONTAL) { widget_length = gtk_widget_get_allocated_width (widget); cairo_translate (cr, (widget_length - indicator_length) / 2, 0); } else { widget_length = gtk_widget_get_allocated_height (widget); cairo_translate (cr, 0, (widget_length - indicator_length) / 2); } x = DOTS_RADIUS_SELECTED; y = DOTS_RADIUS_SELECTED; for (i = 0; i < n_pages; i++) { gdouble progress, radius, opacity; progress = MAX (1 - ABS (position - i), 0); radius = LERP (DOTS_RADIUS, DOTS_RADIUS_SELECTED, progress); opacity = LERP (DOTS_OPACITY, DOTS_OPACITY_SELECTED, progress); cairo_set_source_rgba (cr, color.red, color.green, color.blue, color.alpha * opacity); cairo_arc (cr, x, y, radius, 0, 2 * M_PI); cairo_fill (cr); if (orientation == GTK_ORIENTATION_HORIZONTAL) x += 2 * DOTS_RADIUS_SELECTED + DOTS_SPACING; else y += 2 * DOTS_RADIUS_SELECTED + DOTS_SPACING; } } static gboolean draw_indicators_cb (HdyPaginator *self, cairo_t *cr, GtkWidget *widget) { guint n_pages; gdouble position; g_object_get (self->scrolling_box, "position", &position, "n-pages", &n_pages, NULL); if (n_pages < 2) return GDK_EVENT_PROPAGATE; if (self->orientation == GTK_ORIENTATION_HORIZONTAL && gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) position = n_pages - position - 1; switch (self->indicator_style){ case HDY_PAGINATOR_INDICATOR_STYLE_NONE: break; case HDY_PAGINATOR_INDICATOR_STYLE_DOTS: draw_indicators_dots (widget, cr, self->orientation, position, n_pages); break; case HDY_PAGINATOR_INDICATOR_STYLE_LINES: draw_indicators_lines (widget, cr, self->orientation, position, n_pages); break; default: g_assert_not_reached (); } return GDK_EVENT_PROPAGATE; } static void update_indicators (HdyPaginator *self) { gboolean show_indicators; gint size, margin; show_indicators = (self->indicator_style != HDY_PAGINATOR_INDICATOR_STYLE_NONE); gtk_widget_set_visible (GTK_WIDGET (self->indicators), show_indicators); gtk_widget_set_visible (GTK_WIDGET (self->empty_box), show_indicators && self->center_content); if (!show_indicators) return; switch (self->indicator_style) { case HDY_PAGINATOR_INDICATOR_STYLE_DOTS: size = 2 * DOTS_RADIUS_SELECTED; margin = DOTS_MARGIN; break; case HDY_PAGINATOR_INDICATOR_STYLE_LINES: size = LINE_WIDTH; margin = LINE_MARGIN; break; case HDY_PAGINATOR_INDICATOR_STYLE_NONE: default: g_assert_not_reached (); } g_object_set (self->indicators, "margin", margin, "width-request", size, "height-request", size, NULL); } /* Copied from GtkOrientable. Orientable widgets are supposed * to do this manually via a private GTK function. */ static void set_orientable_style_classes (GtkOrientable *orientable) { GtkStyleContext *context; GtkOrientation orientation; g_return_if_fail (GTK_IS_ORIENTABLE (orientable)); g_return_if_fail (GTK_IS_WIDGET (orientable)); context = gtk_widget_get_style_context (GTK_WIDGET (orientable)); orientation = gtk_orientable_get_orientation (orientable); if (orientation == GTK_ORIENTATION_HORIZONTAL) { gtk_style_context_add_class (context, GTK_STYLE_CLASS_HORIZONTAL); gtk_style_context_remove_class (context, GTK_STYLE_CLASS_VERTICAL); } else { gtk_style_context_add_class (context, GTK_STYLE_CLASS_VERTICAL); gtk_style_context_remove_class (context, GTK_STYLE_CLASS_HORIZONTAL); } } static void update_orientation (HdyPaginator *self) { GtkOrientation opposite; gboolean reversed; if (!self->scrolling_box) return; opposite = (self->orientation == GTK_ORIENTATION_HORIZONTAL) ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL; reversed = self->orientation == GTK_ORIENTATION_HORIZONTAL && gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; g_object_set (self->scrolling_box, "orientation", self->orientation, NULL); g_object_set (self->tracker, "orientation", self->orientation, "reversed", reversed, NULL); g_object_set (self->box, "orientation", opposite, NULL); set_orientable_style_classes (GTK_ORIENTABLE (self)); set_orientable_style_classes (GTK_ORIENTABLE (self->scrolling_box)); gtk_widget_queue_draw (GTK_WIDGET (self->indicators)); } static gboolean scroll_timeout_cb (HdyPaginator *self) { self->can_scroll = TRUE; return G_SOURCE_REMOVE; } static gboolean handle_discrete_scroll_event (HdyPaginator *self, GdkEvent *event) { GdkDevice *source_device; GdkInputSource input_source; GdkScrollDirection direction; gdouble dx, dy; gint index; gboolean allow_vertical; GtkOrientation orientation; guint duration; if (!self->can_scroll) return GDK_EVENT_PROPAGATE; if (!hdy_paginator_get_interactive (self)) return GDK_EVENT_PROPAGATE; if (event->type != GDK_SCROLL) return GDK_EVENT_PROPAGATE; source_device = gdk_event_get_source_device (event); input_source = gdk_device_get_source (source_device); if (input_source == GDK_SOURCE_TOUCHPAD) return GDK_EVENT_PROPAGATE; /* Mice often don't have easily accessible horizontal scrolling, * hence allow vertical mouse scrolling regardless of orientation */ allow_vertical = (input_source == GDK_SOURCE_MOUSE); if (gdk_event_get_scroll_direction (event, &direction)) { dx = 0; dy = 0; switch (direction) { case GDK_SCROLL_UP: dy = -1; break; case GDK_SCROLL_DOWN: dy = 1; break; case GDK_SCROLL_LEFT: dy = -1; break; case GDK_SCROLL_RIGHT: dy = 1; break; case GDK_SCROLL_SMOOTH: g_assert_not_reached (); default: return GDK_EVENT_PROPAGATE; } } else { gdk_event_get_scroll_deltas (event, &dx, &dy); } orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self)); index = 0; if (orientation == GTK_ORIENTATION_VERTICAL || allow_vertical) { if (dy > 0) index++; else if (dy < 0) index--; } if (orientation == GTK_ORIENTATION_HORIZONTAL && index == 0) { if (dx > 0) index++; else if (dx < 0) index--; } if (index == 0) return GDK_EVENT_PROPAGATE; index += (gint) round (hdy_paginator_get_position (self)); index = CLAMP (index, 0, (gint) hdy_paginator_get_n_pages (self) - 1); hdy_paginator_scroll_to (self, hdy_paginator_box_get_nth_child (self->scrolling_box, index)); /* Don't allow the delay to go lower than 250ms */ duration = MIN (self->animation_duration, DEFAULT_DURATION); self->can_scroll = FALSE; g_timeout_add (duration, (GSourceFunc) scroll_timeout_cb, self); return GDK_EVENT_STOP; } static gboolean captured_event_cb (HdyPaginator *self, GdkEvent *event) { if (hdy_swipe_tracker_captured_event (self->tracker, event)) return GDK_EVENT_STOP; return handle_discrete_scroll_event (self, event); } static void hdy_paginator_destroy (GtkWidget *widget) { HdyPaginator *self = HDY_PAGINATOR (widget); if (self->box) { gtk_widget_destroy (GTK_WIDGET (self->box)); self->box = NULL; } GTK_WIDGET_CLASS (hdy_paginator_parent_class)->destroy (widget); } static void hdy_paginator_direction_changed (GtkWidget *widget, GtkTextDirection previous_direction) { HdyPaginator *self = HDY_PAGINATOR (widget); update_orientation (self); } static void hdy_paginator_add (GtkContainer *container, GtkWidget *widget) { HdyPaginator *self = HDY_PAGINATOR (container); if (self->scrolling_box) gtk_container_add (GTK_CONTAINER (self->scrolling_box), widget); else GTK_CONTAINER_CLASS (hdy_paginator_parent_class)->add (container, widget); } static void hdy_paginator_remove (GtkContainer *container, GtkWidget *widget) { HdyPaginator *self = HDY_PAGINATOR (container); if (self->scrolling_box) gtk_container_remove (GTK_CONTAINER (self->scrolling_box), widget); else GTK_CONTAINER_CLASS (hdy_paginator_parent_class)->remove (container, widget); } static void hdy_paginator_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { HdyPaginator *self = HDY_PAGINATOR (container); if (include_internals) (* callback) (GTK_WIDGET (self->box), callback_data); else if (self->scrolling_box) gtk_container_foreach (GTK_CONTAINER (self->scrolling_box), callback, callback_data); } static void hdy_paginator_constructed (GObject *object) { HdyPaginator *self = (HdyPaginator *)object; update_orientation (self); G_OBJECT_CLASS (hdy_paginator_parent_class)->constructed (object); } static void hdy_paginator_dispose (GObject *object) { HdyPaginator *self = (HdyPaginator *)object; if (self->tracker) { g_clear_object (&self->tracker); g_object_set_data (object, "captured-event-handler", NULL); } if (self->scroll_timeout_id != 0) { g_source_remove (self->scroll_timeout_id); self->scroll_timeout_id = 0; } G_OBJECT_CLASS (hdy_paginator_parent_class)->dispose (object); } static void hdy_paginator_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyPaginator *self = HDY_PAGINATOR (object); switch (prop_id) { case PROP_N_PAGES: g_value_set_uint (value, hdy_paginator_get_n_pages (self)); break; case PROP_POSITION: g_value_set_double (value, hdy_paginator_get_position (self)); break; case PROP_INTERACTIVE: g_value_set_boolean (value, hdy_paginator_get_interactive (self)); break; case PROP_INDICATOR_STYLE: g_value_set_enum (value, hdy_paginator_get_indicator_style (self)); break; case PROP_INDICATOR_SPACING: g_value_set_uint (value, hdy_paginator_get_indicator_spacing (self)); break; case PROP_CENTER_CONTENT: g_value_set_boolean (value, hdy_paginator_get_center_content (self)); break; case PROP_SPACING: g_value_set_uint (value, hdy_paginator_get_spacing (self)); break; case PROP_ALLOW_MOUSE_DRAG: g_value_set_boolean (value, hdy_paginator_get_allow_mouse_drag (self)); break; case PROP_ORIENTATION: g_value_set_enum (value, self->orientation); break; case PROP_ANIMATION_DURATION: g_value_set_uint (value, hdy_paginator_get_animation_duration (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_paginator_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyPaginator *self = HDY_PAGINATOR (object); switch (prop_id) { case PROP_INTERACTIVE: hdy_paginator_set_interactive (self, g_value_get_boolean (value)); break; case PROP_INDICATOR_STYLE: hdy_paginator_set_indicator_style (self, g_value_get_enum (value)); break; case PROP_INDICATOR_SPACING: hdy_paginator_set_indicator_spacing (self, g_value_get_uint (value)); break; case PROP_CENTER_CONTENT: hdy_paginator_set_center_content (self, g_value_get_boolean (value)); break; case PROP_SPACING: hdy_paginator_set_spacing (self, g_value_get_uint (value)); break; case PROP_ANIMATION_DURATION: hdy_paginator_set_animation_duration (self, g_value_get_uint (value)); break; case PROP_ALLOW_MOUSE_DRAG: hdy_paginator_set_allow_mouse_drag (self, g_value_get_boolean (value)); break; case PROP_ORIENTATION: { GtkOrientation orientation = g_value_get_enum (value); if (orientation != self->orientation) { self->orientation = orientation; update_orientation (self); g_object_notify (G_OBJECT (self), "orientation"); } } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_paginator_swipeable_init (HdySwipeableInterface *iface) { iface->switch_child = hdy_paginator_switch_child; iface->begin_swipe = hdy_paginator_begin_swipe; iface->update_swipe = hdy_paginator_update_swipe; iface->end_swipe = hdy_paginator_end_swipe; } static void hdy_paginator_class_init (HdyPaginatorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->constructed = hdy_paginator_constructed; object_class->dispose = hdy_paginator_dispose; object_class->get_property = hdy_paginator_get_property; object_class->set_property = hdy_paginator_set_property; widget_class->destroy = hdy_paginator_destroy; widget_class->direction_changed = hdy_paginator_direction_changed; container_class->add = hdy_paginator_add; container_class->remove = hdy_paginator_remove; container_class->forall = hdy_paginator_forall; /** * HdyPaginator:n-pages: * * The number of pages in a #HdyPaginator * * Since: 0.0.11 */ props[PROP_N_PAGES] = g_param_spec_uint ("n-pages", _("Number of pages"), _("Number of pages"), 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPaginator:position: * * Current scrolling position, unitless. 1 matches 1 page. Use * hdy_paginator_scroll_to() for changing it. * * Since: 0.0.11 */ props[PROP_POSITION] = g_param_spec_double ("position", _("Position"), _("Current scrolling position"), 0, G_MAXDOUBLE, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPaginator:interactive: * * Whether @self can be navigated. This can be used to temporarily disable * a #HdyPaginator to only allow navigating it in a certain state. * * Since: 0.0.11 */ props[PROP_INTERACTIVE] = g_param_spec_boolean ("interactive", _("Interactive"), _("Whether the widget can be swiped"), TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPaginator:indicator-style: * * The style of page indicators. Depending on orientation, they are displayed * below or besides the pages. If the pages are meant to be centered, * #HdyPaginator:center-content can be used to compensate for that. * * Since: 0.0.11 */ props[PROP_INDICATOR_STYLE] = g_param_spec_enum ("indicator-style", _("Indicator style"), _("Page indicator style"), HDY_TYPE_PAGINATOR_INDICATOR_STYLE, HDY_PAGINATOR_INDICATOR_STYLE_NONE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPaginator:indicator-spacing: * * Spacing between content and page indicators. Does nothing if * #HdyPaginator:indicator-style is @HDY_PAGINATOR_INDICATOR_STYLE_NONE. * * Since: 0.0.11 */ props[PROP_INDICATOR_SPACING] = g_param_spec_uint ("indicator-spacing", _("Indicator spacing"), _("Spacing between content and indicators"), 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPaginator:center-content: * * Whether the #HdyPaginator is centering pages. If * #HdyPaginator:indicator-style is @HDY_PAGINATOR_INDICATOR_STYLE_NONE, * centering does nothing, otherwise it adds whitespace to the left or above * the pages to compensate for the indicators. * * Since: 0.0.11 */ props[PROP_CENTER_CONTENT] = g_param_spec_boolean ("center-content", _("Center content"), _("Whether to center pages to compensate for indicators"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPaginator:spacing: * * Spacing between pages in pixels. * * Since: 0.0.11 */ props[PROP_SPACING] = g_param_spec_uint ("spacing", _("Spacing"), _("Spacing between pages"), 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPaginator:animation-duration: * * Animation duration in milliseconds, used by hdy_paginator_scroll_to(). * * Since: 0.0.11 */ props[PROP_ANIMATION_DURATION] = g_param_spec_uint ("animation-duration", _("Animation duration"), _("Default animation duration"), 0, G_MAXUINT, DEFAULT_DURATION, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPaginator:allow-mouse-drag: * * Sets whether the #HdyPaginator can be dragged with mouse pointer. If the * value is %FALSE, dragging is only available on touch. * * This should usually be %FALSE. * * Since: 0.0.12 */ props[PROP_ALLOW_MOUSE_DRAG] = g_param_spec_boolean ("allow-mouse-drag", _("Allow mouse drag"), _("Whether to allow dragging with mouse pointer"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); g_object_class_install_properties (object_class, LAST_PROP, props); /** * HdyPaginator::page-changed: * @self: The #HdyPaginator instance * @index: Current page * * This signal is emitted after a page has been changed. This can be used to * implement "infinite scrolling" by connecting to this signal and amending * the pages. * * Since: 0.0.12 */ signals[SIGNAL_PAGE_CHANGED] = g_signal_new ("page-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-paginator.ui"); gtk_widget_class_bind_template_child (widget_class, HdyPaginator, box); gtk_widget_class_bind_template_child (widget_class, HdyPaginator, empty_box); gtk_widget_class_bind_template_child (widget_class, HdyPaginator, scrolling_box); gtk_widget_class_bind_template_child (widget_class, HdyPaginator, indicators); gtk_widget_class_bind_template_callback (widget_class, draw_indicators_cb); gtk_widget_class_bind_template_callback (widget_class, notify_n_pages_cb); gtk_widget_class_bind_template_callback (widget_class, notify_position_cb); gtk_widget_class_bind_template_callback (widget_class, notify_spacing_cb); gtk_widget_class_bind_template_callback (widget_class, animation_stopped_cb); gtk_widget_class_set_css_name (widget_class, "hdypaginator"); } static void hdy_paginator_init (HdyPaginator *self) { g_type_ensure (HDY_TYPE_PAGINATOR_BOX); gtk_widget_init_template (GTK_WIDGET (self)); self->animation_duration = DEFAULT_DURATION; self->tracker = hdy_swipe_tracker_new (HDY_SWIPEABLE (self)); self->can_scroll = TRUE; /* * HACK: GTK3 has no other way to get events on capture phase. * This is a reimplementation of _gtk_widget_set_captured_event_handler(), * which is private. In GTK4 it can be replaced with GtkEventControllerLegacy * with capture propagation phase */ g_object_set_data (G_OBJECT (self), "captured-event-handler", captured_event_cb); } /** * hdy_paginator_new: * * Create a new #HdyPaginator widget. * * Returns: The newly created #HdyPaginator widget * * Since: 0.0.11 */ HdyPaginator * hdy_paginator_new (void) { return g_object_new (HDY_TYPE_PAGINATOR, NULL); } /** * hdy_paginator_prepend: * @self: a #HdyPaginator * @child: a widget to add * * Prepends @child to @self * * Since: 0.0.11 */ void hdy_paginator_prepend (HdyPaginator *self, GtkWidget *widget) { g_return_if_fail (HDY_IS_PAGINATOR (self)); hdy_paginator_box_insert (self->scrolling_box, widget, 0); } /** * hdy_paginator_insert: * @self: a #HdyPaginator * @child: a widget to add * @position: the position to insert @child in. * * Inserts @child into @self at position @position. * * If position is -1, or larger than the number of pages, * @child will be appended to the end. * * Since: 0.0.11 */ void hdy_paginator_insert (HdyPaginator *self, GtkWidget *widget, gint position) { g_return_if_fail (HDY_IS_PAGINATOR (self)); hdy_paginator_box_insert (self->scrolling_box, widget, position); } /** * hdy_paginator_reorder: * @self: a #HdyPaginator * @child: a widget to add * @position: the position to move @child to. * * Moves @child into position @position. * * If position is -1, or larger than the number of pages, @child will be moved * to the end. * * Since: 0.0.11 */ void hdy_paginator_reorder (HdyPaginator *self, GtkWidget *child, gint position) { g_return_if_fail (HDY_IS_PAGINATOR (self)); g_return_if_fail (GTK_IS_WIDGET (child)); hdy_paginator_box_reorder (self->scrolling_box, child, position); } /** * hdy_paginator_scroll_to: * @self: a #HdyPaginator * @widget: a child of @self * * Scrolls to @widget position with an animation. * #HdyPaginator:animation-duration property can be used for controlling the * duration. * * Since: 0.0.11 */ void hdy_paginator_scroll_to (HdyPaginator *self, GtkWidget *widget) { g_return_if_fail (HDY_IS_PAGINATOR (self)); hdy_paginator_scroll_to_full (self, widget, self->animation_duration); } /** * hdy_paginator_scroll_to_full: * @self: a #HdyPaginator * @widget: a child of @self * @duration: animation duration in milliseconds * * Scrolls to @widget position with an animation. * * Since: 0.0.11 */ void hdy_paginator_scroll_to_full (HdyPaginator *self, GtkWidget *widget, gint64 duration) { GList *children; gint n; g_return_if_fail (HDY_IS_PAGINATOR (self)); children = gtk_container_get_children (GTK_CONTAINER (self->scrolling_box)); n = g_list_index (children, widget); g_list_free (children); hdy_paginator_box_scroll_to (self->scrolling_box, widget, duration); hdy_swipeable_emit_switch_child (HDY_SWIPEABLE (self), n, duration); } /** * hdy_paginator_get_n_pages: * @self: a #HdyPaginator * * Gets the number of pages in @self. * * Returns: The number of pages in @self * * Since: 0.0.11 */ guint hdy_paginator_get_n_pages (HdyPaginator *self) { g_return_val_if_fail (HDY_IS_PAGINATOR (self), 0); return hdy_paginator_box_get_n_pages (self->scrolling_box); } /** * hdy_paginator_get_position: * @self: a #HdyPaginator * * Gets current scroll position in @self. It's unitless, 1 matches 1 page. * * Returns: The scroll position * * Since: 0.0.11 */ gdouble hdy_paginator_get_position (HdyPaginator *self) { g_return_val_if_fail (HDY_IS_PAGINATOR (self), 0); return hdy_paginator_box_get_position (self->scrolling_box); } /** * hdy_paginator_get_interactive * @self: a #HdyPaginator * * Gets whether @self can be navigated. * * Returns: %TRUE if @self can be swiped * * Since: 0.0.11 */ gboolean hdy_paginator_get_interactive (HdyPaginator *self) { g_return_val_if_fail (HDY_IS_PAGINATOR (self), FALSE); return hdy_swipe_tracker_get_enabled (self->tracker); } /** * hdy_paginator_set_interactive * @self: a #HdyPaginator * @interactive: whether @self can be swiped. * * Sets whether @self can be navigated. This can be used to temporarily disable * a #HdyPaginator to only allow swiping in a certain state. * * Since: 0.0.11 */ void hdy_paginator_set_interactive (HdyPaginator *self, gboolean interactive) { g_return_if_fail (HDY_IS_PAGINATOR (self)); interactive = !!interactive; if (hdy_swipe_tracker_get_enabled (self->tracker) == interactive) return; hdy_swipe_tracker_set_enabled (self->tracker, interactive); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INTERACTIVE]); } /** * hdy_paginator_get_indicator_style * @self: a #HdyPaginator * * Gets the current page indicator style. * * Returns: the current indicator style * * Since: 0.0.11 */ HdyPaginatorIndicatorStyle hdy_paginator_get_indicator_style (HdyPaginator *self) { g_return_val_if_fail (HDY_IS_PAGINATOR (self), FALSE); return self->indicator_style; } /** * hdy_paginator_set_indicator_style * @self: a #HdyPaginator * @style: indicator style to use * * Sets style of page indicators. Depending on orientation, they are displayed * below or besides the pages. If the pages are meant to be centered, * #HdyPaginator:center-content can be used to compensate for that. * * Since: 0.0.11 */ void hdy_paginator_set_indicator_style (HdyPaginator *self, HdyPaginatorIndicatorStyle style) { g_return_if_fail (HDY_IS_PAGINATOR (self)); if (self->indicator_style == style) return; self->indicator_style = style; update_indicators (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INDICATOR_STYLE]); } /** * hdy_paginator_get_indicator_spacing: * @self: a #HdyPaginator * * Gets spacing between content and page indicators. * * Returns: Spacing between content and indicators * * Since: 0.0.11 */ guint hdy_paginator_get_indicator_spacing (HdyPaginator *self) { g_return_val_if_fail (HDY_IS_PAGINATOR (self), 0); return self->indicator_spacing; } /** * hdy_paginator_set_indicator_spacing: * @self: a #HdyPaginator * @spacing: the new spacing value * * Sets spacing between content and page indicators. Does nothing if * #HdyPaginator:indicator-style is @HDY_PAGINATOR_INDICATOR_STYLE_NONE. * * Since: 0.0.11 */ void hdy_paginator_set_indicator_spacing (HdyPaginator *self, guint spacing) { g_return_if_fail (HDY_IS_PAGINATOR (self)); if (self->indicator_spacing == spacing) return; self->indicator_spacing = spacing; gtk_box_set_spacing (self->box, spacing); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INDICATOR_SPACING]); } /** * hdy_paginator_get_center_content * @self: a #HdyPaginator * * Sets whether @self is centering pages. * * Returns: %TRUE if @self is centering pages * * Since: 0.0.11 */ gboolean hdy_paginator_get_center_content (HdyPaginator *self) { g_return_val_if_fail (HDY_IS_PAGINATOR (self), FALSE); return self->center_content; } /** * hdy_paginator_set_center_content * @self: a #HdyPaginator * @center_content: whether @self should center contents * * Sets whether @self is centering content. If #HdyPaginator:indicator-style is * @HDY_PAGINATOR_INDICATOR_STYLE_NONE, centering does nothing, otherwise it * adds whitespace to the left or above the pages to compensate for the * indicators. * * Since: 0.0.11 */ void hdy_paginator_set_center_content (HdyPaginator *self, gboolean center_content) { g_return_if_fail (HDY_IS_PAGINATOR (self)); center_content = !!center_content; if (self->center_content == center_content) return; self->center_content = center_content; update_indicators (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CENTER_CONTENT]); } /** * hdy_paginator_get_spacing: * @self: a #HdyPaginator * * Gets spacing between pages in pixels. * * Returns: Spacing between pages * * Since: 0.0.11 */ guint hdy_paginator_get_spacing (HdyPaginator *self) { g_return_val_if_fail (HDY_IS_PAGINATOR (self), 0); return hdy_paginator_box_get_spacing (self->scrolling_box); } /** * hdy_paginator_set_spacing: * @self: a #HdyPaginator * @spacing: the new spacing value * * Sets spacing between pages in pixels. * * Since: 0.0.11 */ void hdy_paginator_set_spacing (HdyPaginator *self, guint spacing) { g_return_if_fail (HDY_IS_PAGINATOR (self)); hdy_paginator_box_set_spacing (self->scrolling_box, spacing); } /** * hdy_paginator_get_animation_duration: * @self: a #HdyPaginator * * Gets animation duration used by hdy_paginator_scroll_to(). * * Returns: Animation duration in milliseconds * * Since: 0.0.11 */ guint hdy_paginator_get_animation_duration (HdyPaginator *self) { g_return_val_if_fail (HDY_IS_PAGINATOR (self), 0); return self->animation_duration; } /** * hdy_paginator_set_animation_duration: * @self: a #HdyPaginator * @duration: animation duration in milliseconds * * Sets animation duration used by hdy_paginator_scroll_to(). * * Since: 0.0.11 */ void hdy_paginator_set_animation_duration (HdyPaginator *self, guint duration) { g_return_if_fail (HDY_IS_PAGINATOR (self)); if (self->animation_duration == duration) return; self->animation_duration = duration; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ANIMATION_DURATION]); } /** * hdy_paginator_get_allow_mouse_drag: * @self: a #HdyPaginator * * Sets whether @self can be dragged with mouse pointer * * Returns: %TRUE if @self can be dragged with mouse * * Since: 0.0.12 */ gboolean hdy_paginator_get_allow_mouse_drag (HdyPaginator *self) { g_return_val_if_fail (HDY_IS_PAGINATOR (self), FALSE); return hdy_swipe_tracker_get_allow_mouse_drag (self->tracker); } /** * hdy_paginator_set_allow_mouse_drag: * @self: a #HdyPaginator * @allow_mouse_drag: whether @self can be dragged with mouse pointer * * Sets whether @self can be dragged with mouse pointer. If @allow_mouse_drag * is %FALSE, dragging is only available on touch. * * This should usually be %FALSE. * * Since: 0.0.12 */ void hdy_paginator_set_allow_mouse_drag (HdyPaginator *self, gboolean allow_mouse_drag) { g_return_if_fail (HDY_IS_PAGINATOR (self)); allow_mouse_drag = !!allow_mouse_drag; hdy_swipe_tracker_set_allow_mouse_drag (self->tracker, allow_mouse_drag); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ALLOW_MOUSE_DRAG]); } libhandy-0.0.13/src/hdy-paginator.h000066400000000000000000000060351360136463700171100ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include "hdy-enums.h" G_BEGIN_DECLS #define HDY_TYPE_PAGINATOR (hdy_paginator_get_type()) G_DECLARE_FINAL_TYPE (HdyPaginator, hdy_paginator, HDY, PAGINATOR, GtkEventBox) typedef enum { HDY_PAGINATOR_INDICATOR_STYLE_NONE, HDY_PAGINATOR_INDICATOR_STYLE_DOTS, HDY_PAGINATOR_INDICATOR_STYLE_LINES, } HdyPaginatorIndicatorStyle; HdyPaginator *hdy_paginator_new (void); void hdy_paginator_prepend (HdyPaginator *self, GtkWidget *child); void hdy_paginator_insert (HdyPaginator *self, GtkWidget *child, gint position); void hdy_paginator_reorder (HdyPaginator *self, GtkWidget *child, gint position); void hdy_paginator_scroll_to (HdyPaginator *self, GtkWidget *widget); void hdy_paginator_scroll_to_full (HdyPaginator *self, GtkWidget *widget, gint64 duration); guint hdy_paginator_get_n_pages (HdyPaginator *self); gdouble hdy_paginator_get_position (HdyPaginator *self); gboolean hdy_paginator_get_interactive (HdyPaginator *self); void hdy_paginator_set_interactive (HdyPaginator *self, gboolean interactive); HdyPaginatorIndicatorStyle hdy_paginator_get_indicator_style (HdyPaginator *self); void hdy_paginator_set_indicator_style (HdyPaginator *self, HdyPaginatorIndicatorStyle style); guint hdy_paginator_get_indicator_spacing (HdyPaginator *self); void hdy_paginator_set_indicator_spacing (HdyPaginator *self, guint spacing); gboolean hdy_paginator_get_center_content (HdyPaginator *self); void hdy_paginator_set_center_content (HdyPaginator *self, gboolean center_content); guint hdy_paginator_get_spacing (HdyPaginator *self); void hdy_paginator_set_spacing (HdyPaginator *self, guint spacing); guint hdy_paginator_get_animation_duration (HdyPaginator *self); void hdy_paginator_set_animation_duration (HdyPaginator *self, guint duration); gboolean hdy_paginator_get_allow_mouse_drag (HdyPaginator *self); void hdy_paginator_set_allow_mouse_drag (HdyPaginator *self, gboolean allow_mouse_drag); G_END_DECLS libhandy-0.0.13/src/hdy-paginator.ui000066400000000000000000000031421360136463700172720ustar00rootroot00000000000000 both libhandy-0.0.13/src/hdy-preferences-group-private.h000066400000000000000000000005021360136463700222200ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "hdy-preferences-group.h" G_BEGIN_DECLS void hdy_preferences_group_add_preferences_to_model (HdyPreferencesGroup *self, GListStore *model); G_END_DECLS libhandy-0.0.13/src/hdy-preferences-group.c000066400000000000000000000307041360136463700205520ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-preferences-group-private.h" #include "hdy-list-box.h" #include "hdy-preferences-row.h" /** * SECTION:hdy-preferences-group * @short_description: A group gathering preferences rows. * @Title: HdyPreferencesGroup * * A #HdyPreferencesGroup represents a group or tightly related preferences, * which in turn are represented by HdyPreferencesRow. * * To summarize the role of the preferences it gathers, a group can have both a * title and a description. The title will be used by #HdyPreferencesWindow to * let the user look for a preference. * * Since: 0.0.10 */ typedef struct { GtkLabel *description; GtkListBox *listbox; GtkBox *listbox_box; GtkLabel *title; } HdyPreferencesGroupPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyPreferencesGroup, hdy_preferences_group, GTK_TYPE_BOX) enum { PROP_0, PROP_DESCRIPTION, PROP_TITLE, LAST_PROP, }; static GParamSpec *props[LAST_PROP]; static void update_title_visibility (HdyPreferencesGroup *self) { HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self); /* Show the listbox only if it has children to avoid having showing the * listbox as an empty frame, parasiting the look of non-GtkListBoxRow * children. */ gtk_widget_set_visible (GTK_WIDGET (priv->title), gtk_label_get_text (priv->title) != NULL && g_strcmp0 (gtk_label_get_text (priv->title), "") != 0); } static void update_description_visibility (HdyPreferencesGroup *self) { HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self); gtk_widget_set_visible (GTK_WIDGET (priv->description), gtk_label_get_text (priv->description) != NULL && g_strcmp0 (gtk_label_get_text (priv->description), "") != 0); } static void update_listbox_visibility (HdyPreferencesGroup *self) { HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self); g_autoptr(GList) children = NULL; /* We must wait until listob has been built and added. */ if (priv->listbox == NULL) return; children = gtk_container_get_children (GTK_CONTAINER (priv->listbox)); gtk_widget_set_visible (GTK_WIDGET (priv->listbox), children != NULL); } typedef struct { HdyPreferencesGroup *group; GtkCallback callback; gpointer callback_data; } ForallData; static void for_non_internal_child (GtkWidget *widget, gpointer callback_data) { ForallData *data = callback_data; HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (data->group); if (widget != (GtkWidget *) priv->listbox) data->callback (widget, data->callback_data); } static void hdy_preferences_group_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (container); HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self); ForallData data; if (include_internals) { GTK_CONTAINER_CLASS (hdy_preferences_group_parent_class)->forall (GTK_CONTAINER (self), include_internals, callback, callback_data); return; } data.group = self; data.callback = callback; data.callback_data = callback_data; if (priv->listbox) GTK_CONTAINER_GET_CLASS (priv->listbox)->forall (GTK_CONTAINER (priv->listbox), include_internals, callback, callback_data); if (priv->listbox_box) GTK_CONTAINER_GET_CLASS (priv->listbox_box)->forall (GTK_CONTAINER (priv->listbox_box), include_internals, for_non_internal_child, &data); } static void hdy_preferences_group_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (object); switch (prop_id) { case PROP_DESCRIPTION: g_value_set_string (value, hdy_preferences_group_get_description (self)); break; case PROP_TITLE: g_value_set_string (value, hdy_preferences_group_get_title (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_preferences_group_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (object); switch (prop_id) { case PROP_DESCRIPTION: hdy_preferences_group_set_description (self, g_value_get_string (value)); break; case PROP_TITLE: hdy_preferences_group_set_title (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_preferences_group_dispose (GObject *object) { HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (object); HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self); /* * Since we overload forall(), the inherited destroy() won't work as normal. * Remove internal widgets ourself. */ g_clear_pointer ((GtkWidget **) &priv->description, gtk_widget_destroy); g_clear_pointer ((GtkWidget **) &priv->listbox, gtk_widget_destroy); g_clear_pointer ((GtkWidget **) &priv->listbox_box, gtk_widget_destroy); g_clear_pointer ((GtkWidget **) &priv->title, gtk_widget_destroy); G_OBJECT_CLASS (hdy_preferences_group_parent_class)->dispose (object); } static void hdy_preferences_group_add (GtkContainer *container, GtkWidget *child) { HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (container); HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self); if (priv->title == NULL || priv->description == NULL || priv->listbox_box == NULL) { GTK_CONTAINER_CLASS (hdy_preferences_group_parent_class)->add (container, child); return; } if (HDY_IS_PREFERENCES_ROW (child)) gtk_container_add (GTK_CONTAINER (priv->listbox), child); else gtk_container_add (GTK_CONTAINER (priv->listbox_box), child); } static void hdy_preferences_group_class_init (HdyPreferencesGroupClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->get_property = hdy_preferences_group_get_property; object_class->set_property = hdy_preferences_group_set_property; object_class->dispose = hdy_preferences_group_dispose; container_class->add = hdy_preferences_group_add; container_class->forall = hdy_preferences_group_forall; /** * HdyPreferencesGroup:description: * * The description for this group of preferences. * * Since: 0.0.10 */ props[PROP_DESCRIPTION] = g_param_spec_string ("description", _("Description"), _("Description"), "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * HdyPreferencesGroup:title: * * The title for this group of preferences. * * Since: 0.0.10 */ props[PROP_TITLE] = g_param_spec_string ("title", _("Title"), _("Title"), "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_css_name (widget_class, "hdypreferencesgroup"); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-preferences-group.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesGroup, description); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesGroup, listbox); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesGroup, listbox_box); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesGroup, title); gtk_widget_class_bind_template_callback (widget_class, update_listbox_visibility); } static void hdy_preferences_group_init (HdyPreferencesGroup *self) { HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self); g_autoptr (GtkCssProvider) provider = gtk_css_provider_new (); gtk_widget_init_template (GTK_WIDGET (self)); gtk_list_box_set_header_func (priv->listbox, hdy_list_box_separator_header, NULL, NULL); update_description_visibility (self); update_title_visibility (self); update_listbox_visibility (self); gtk_css_provider_load_from_resource (provider, "/sm/puri/handy/style/hdy-text.css"); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->title)), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_FALLBACK); } /** * hdy_preferences_group_new: * * Creates a new #HdyPreferencesGroup. * * Returns: a new #HdyPreferencesGroup * * Since: 0.0.10 */ HdyPreferencesGroup * hdy_preferences_group_new (void) { return g_object_new (HDY_TYPE_PREFERENCES_GROUP, NULL); } /** * hdy_preferences_group_get_title: * @self: a #HdyPreferencesGroup * * Gets the title of @self. * * Returns: the title of @self. * * Since: 0.0.10 */ const gchar * hdy_preferences_group_get_title (HdyPreferencesGroup *self) { HdyPreferencesGroupPrivate *priv; g_return_val_if_fail (HDY_IS_PREFERENCES_GROUP (self), NULL); priv = hdy_preferences_group_get_instance_private (self); return gtk_label_get_text (priv->title); } /** * hdy_preferences_group_set_title: * @self: a #HdyPreferencesGroup * @title: the title * * Sets the title for @self. * * Since: 0.0.10 */ void hdy_preferences_group_set_title (HdyPreferencesGroup *self, const gchar *title) { HdyPreferencesGroupPrivate *priv; g_return_if_fail (HDY_IS_PREFERENCES_GROUP (self)); priv = hdy_preferences_group_get_instance_private (self); if (g_strcmp0 (gtk_label_get_label (priv->title), title) == 0) return; gtk_label_set_label (priv->title, title); update_title_visibility (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]); } /** * hdy_preferences_group_get_description: * @self: a #HdyPreferencesGroup * * * Returns: the description of @self. * * Since: 0.0.10 */ const gchar * hdy_preferences_group_get_description (HdyPreferencesGroup *self) { HdyPreferencesGroupPrivate *priv; g_return_val_if_fail (HDY_IS_PREFERENCES_GROUP (self), NULL); priv = hdy_preferences_group_get_instance_private (self); return gtk_label_get_text (priv->description); } /** * hdy_preferences_group_set_description: * @self: a #HdyPreferencesGroup * @description: the description * * Sets the description for @self. * * Since: 0.0.10 */ void hdy_preferences_group_set_description (HdyPreferencesGroup *self, const gchar *description) { HdyPreferencesGroupPrivate *priv; g_return_if_fail (HDY_IS_PREFERENCES_GROUP (self)); priv = hdy_preferences_group_get_instance_private (self); if (g_strcmp0 (gtk_label_get_label (priv->description), description) == 0) return; gtk_label_set_label (priv->description, description); update_description_visibility (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DESCRIPTION]); } static void add_preferences_to_model (HdyPreferencesRow *row, GListStore *model) { g_assert (HDY_IS_PREFERENCES_ROW (row)); g_assert (G_IS_LIST_STORE (model)); g_list_store_append (model, row); } /** * hdy_preferences_group_add_preferences_to_model: (skip) * @self: a #HdyPreferencesGroup * @model: the model * * Add preferences from @self to the model. * * Since: 0.0.10 */ void hdy_preferences_group_add_preferences_to_model (HdyPreferencesGroup *self, GListStore *model) { HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self); g_return_if_fail (HDY_IS_PREFERENCES_GROUP (self)); g_return_if_fail (G_IS_LIST_STORE (model)); gtk_container_foreach (GTK_CONTAINER (priv->listbox), (GtkCallback) add_preferences_to_model, model); } libhandy-0.0.13/src/hdy-preferences-group.h000066400000000000000000000021141360136463700205510ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_PREFERENCES_GROUP (hdy_preferences_group_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyPreferencesGroup, hdy_preferences_group, HDY, PREFERENCES_GROUP, GtkBox) /** * HdyPreferencesGroupClass * @parent_class: The parent class */ struct _HdyPreferencesGroupClass { GtkBoxClass parent_class; }; HdyPreferencesGroup *hdy_preferences_group_new (void); const gchar *hdy_preferences_group_get_title (HdyPreferencesGroup *self); void hdy_preferences_group_set_title (HdyPreferencesGroup *self, const gchar *title); const gchar *hdy_preferences_group_get_description (HdyPreferencesGroup *self); void hdy_preferences_group_set_description (HdyPreferencesGroup *self, const gchar *description); G_END_DECLS libhandy-0.0.13/src/hdy-preferences-group.ui000066400000000000000000000031541360136463700207440ustar00rootroot00000000000000 libhandy-0.0.13/src/hdy-preferences-page-private.h000066400000000000000000000004751360136463700220110ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "hdy-preferences-page.h" G_BEGIN_DECLS void hdy_preferences_page_add_preferences_to_model (HdyPreferencesPage *self, GListStore *model); G_END_DECLS libhandy-0.0.13/src/hdy-preferences-page.c000066400000000000000000000200731360136463700203300ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-preferences-page-private.h" #include "hdy-preferences-group-private.h" /** * SECTION:hdy-preferences-page * @short_description: A page from the preferences window. * @Title: HdyPreferencesPage * * The #HdyPreferencesPage widget gathers preferences groups into a single page * of a preferences window. * * Since: 0.0.10 */ typedef struct { GtkBox *box; GtkViewport *viewport; gchar *icon_name; gchar *title; } HdyPreferencesPagePrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyPreferencesPage, hdy_preferences_page, GTK_TYPE_SCROLLED_WINDOW) enum { PROP_0, PROP_ICON_NAME, PROP_TITLE, LAST_PROP, }; static GParamSpec *props[LAST_PROP]; typedef struct { HdyPreferencesPage *preferences_page; GtkCallback callback; gpointer data; } CallbackData; static void hdy_preferences_page_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyPreferencesPage *self = HDY_PREFERENCES_PAGE (object); switch (prop_id) { case PROP_ICON_NAME: g_value_set_string (value, hdy_preferences_page_get_icon_name (self)); break; case PROP_TITLE: g_value_set_string (value, hdy_preferences_page_get_title (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_preferences_page_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyPreferencesPage *self = HDY_PREFERENCES_PAGE (object); switch (prop_id) { case PROP_ICON_NAME: hdy_preferences_page_set_icon_name (self, g_value_get_string (value)); break; case PROP_TITLE: hdy_preferences_page_set_title (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_preferences_page_finalize (GObject *object) { HdyPreferencesPage *self = HDY_PREFERENCES_PAGE (object); HdyPreferencesPagePrivate *priv = hdy_preferences_page_get_instance_private (self); g_clear_pointer (&priv->icon_name, g_free); g_clear_pointer (&priv->title, g_free); G_OBJECT_CLASS (hdy_preferences_page_parent_class)->finalize (object); } static void hdy_preferences_page_add (GtkContainer *container, GtkWidget *child) { HdyPreferencesPage *self = HDY_PREFERENCES_PAGE (container); HdyPreferencesPagePrivate *priv = hdy_preferences_page_get_instance_private (self); if (priv->viewport == NULL) GTK_CONTAINER_CLASS (hdy_preferences_page_parent_class)->add (container, child); else if (HDY_IS_PREFERENCES_GROUP (child)) gtk_container_add (GTK_CONTAINER (priv->box), child); else g_warning ("Can't add children of type %s to %s", G_OBJECT_TYPE_NAME (child), G_OBJECT_TYPE_NAME (container)); } static void hdy_preferences_page_class_init (HdyPreferencesPageClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->get_property = hdy_preferences_page_get_property; object_class->set_property = hdy_preferences_page_set_property; object_class->finalize = hdy_preferences_page_finalize; container_class->add = hdy_preferences_page_add; /** * HdyPreferencesPage:icon-name: * * The icon name for this page of preferences. * * Since: 0.0.10 */ props[PROP_ICON_NAME] = g_param_spec_string ("icon-name", _("Icon name"), _("Icon name"), "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPreferencesPage:title: * * The title for this page of preferences. * * Since: 0.0.10 */ props[PROP_TITLE] = g_param_spec_string ("title", _("Title"), _("Title"), "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-preferences-page.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesPage, box); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesPage, viewport); gtk_widget_class_set_css_name (widget_class, "HdyPreferencesPage"); } static void hdy_preferences_page_init (HdyPreferencesPage *self) { gtk_widget_init_template (GTK_WIDGET (self)); } /** * hdy_preferences_page_new: * * Creates a new #HdyPreferencesPage. * * Returns: a new #HdyPreferencesPage * * Since: 0.0.10 */ HdyPreferencesPage * hdy_preferences_page_new (void) { return g_object_new (HDY_TYPE_PREFERENCES_PAGE, NULL); } /** * hdy_preferences_page_get_icon_name: * @self: a #HdyPreferencesPage * * Gets the icon name for @self, or %NULL. * * Returns: (transfer none) (nullable): the icon name for @self, or %NULL. * * Since: 0.0.10 */ const gchar * hdy_preferences_page_get_icon_name (HdyPreferencesPage *self) { HdyPreferencesPagePrivate *priv; g_return_val_if_fail (HDY_IS_PREFERENCES_PAGE (self), NULL); priv = hdy_preferences_page_get_instance_private (self); return priv->icon_name; } /** * hdy_preferences_page_set_icon_name: * @self: a #HdyPreferencesPage * @icon_name: (nullable): the icon name, or %NULL * * Sets the icon name for @self. * * Since: 0.0.10 */ void hdy_preferences_page_set_icon_name (HdyPreferencesPage *self, const gchar *icon_name) { HdyPreferencesPagePrivate *priv; g_return_if_fail (HDY_IS_PREFERENCES_PAGE (self)); priv = hdy_preferences_page_get_instance_private (self); if (g_strcmp0 (priv->icon_name, icon_name) == 0) return; g_clear_pointer (&priv->icon_name, g_free); priv->icon_name = g_strdup (icon_name); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]); } /** * hdy_preferences_page_get_title: * @self: a #HdyPreferencesPage * * Gets the title of @self, or %NULL. * * Returns: (transfer none) (nullable): the title of the @self, or %NULL. * * Since: 0.0.10 */ const gchar * hdy_preferences_page_get_title (HdyPreferencesPage *self) { HdyPreferencesPagePrivate *priv; g_return_val_if_fail (HDY_IS_PREFERENCES_PAGE (self), NULL); priv = hdy_preferences_page_get_instance_private (self); return priv->title; } /** * hdy_preferences_page_set_title: * @self: a #HdyPreferencesPage * @title: (nullable): the title of the page, or %NULL * * Sets the title of @self. * * Since: 0.0.10 */ void hdy_preferences_page_set_title (HdyPreferencesPage *self, const gchar *title) { HdyPreferencesPagePrivate *priv; g_return_if_fail (HDY_IS_PREFERENCES_PAGE (self)); priv = hdy_preferences_page_get_instance_private (self); if (g_strcmp0 (priv->title, title) == 0) return; g_clear_pointer (&priv->title, g_free); priv->title = g_strdup (title); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]); } /** * hdy_preferences_page_add_preferences_to_model: (skip) * @self: a #HdyPreferencesPage * @model: the model * * Add preferences from @self to the model. * * Since: 0.0.10 */ void hdy_preferences_page_add_preferences_to_model (HdyPreferencesPage *self, GListStore *model) { HdyPreferencesPagePrivate *priv; g_return_if_fail (HDY_IS_PREFERENCES_PAGE (self)); g_return_if_fail (G_IS_LIST_STORE (model)); priv = hdy_preferences_page_get_instance_private (self); gtk_container_foreach (GTK_CONTAINER (priv->box), (GtkCallback) hdy_preferences_group_add_preferences_to_model, model); } libhandy-0.0.13/src/hdy-preferences-page.h000066400000000000000000000021051360136463700203310ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_PREFERENCES_PAGE (hdy_preferences_page_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyPreferencesPage, hdy_preferences_page, HDY, PREFERENCES_PAGE, GtkScrolledWindow) /** * HdyPreferencesPageClass * @parent_class: The parent class */ struct _HdyPreferencesPageClass { GtkScrolledWindowClass parent_class; }; HdyPreferencesPage *hdy_preferences_page_new (void); const gchar *hdy_preferences_page_get_icon_name (HdyPreferencesPage *self); void hdy_preferences_page_set_icon_name (HdyPreferencesPage *self, const gchar *icon_name); const gchar *hdy_preferences_page_get_title (HdyPreferencesPage *self); void hdy_preferences_page_set_title (HdyPreferencesPage *self, const gchar *title); G_END_DECLS libhandy-0.0.13/src/hdy-preferences-page.ui000066400000000000000000000022671360136463700205300ustar00rootroot00000000000000 libhandy-0.0.13/src/hdy-preferences-row.c000066400000000000000000000151361360136463700202270ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-preferences-row.h" /** * SECTION:hdy-preferences-row * @short_description: A #GtkListBox row used to present preferences. * @Title: HdyPreferencesRow * * The #HdyPreferencesRow widget has a title that #HdyPreferencesWindow will use * to let the user look for a preference. It doesn't present the title in any * way and it lets you present the preference as you please. * * #HdyActionRow and its derivatives are convenient to use as preference rows as * they take care of presenting the preference's title while letting you compose * the inputs of the preference around it. * * Since: 0.0.10 */ typedef struct { gchar *title; gboolean use_underline; } HdyPreferencesRowPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyPreferencesRow, hdy_preferences_row, GTK_TYPE_LIST_BOX_ROW) enum { PROP_0, PROP_TITLE, PROP_USE_UNDERLINE, LAST_PROP, }; static GParamSpec *props[LAST_PROP]; static void hdy_preferences_row_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyPreferencesRow *self = HDY_PREFERENCES_ROW (object); switch (prop_id) { case PROP_TITLE: g_value_set_string (value, hdy_preferences_row_get_title (self)); break; case PROP_USE_UNDERLINE: g_value_set_boolean (value, hdy_preferences_row_get_use_underline (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_preferences_row_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyPreferencesRow *self = HDY_PREFERENCES_ROW (object); switch (prop_id) { case PROP_TITLE: hdy_preferences_row_set_title (self, g_value_get_string (value)); break; case PROP_USE_UNDERLINE: hdy_preferences_row_set_use_underline (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_preferences_row_finalize (GObject *object) { HdyPreferencesRow *self = HDY_PREFERENCES_ROW (object); HdyPreferencesRowPrivate *priv = hdy_preferences_row_get_instance_private (self); g_free (priv->title); G_OBJECT_CLASS (hdy_preferences_row_parent_class)->finalize (object); } static void hdy_preferences_row_class_init (HdyPreferencesRowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = hdy_preferences_row_get_property; object_class->set_property = hdy_preferences_row_set_property; object_class->finalize = hdy_preferences_row_finalize; /** * HdyPreferencesRow:title: * * The title of the preference represented by this row. * * Since: 0.0.10 */ props[PROP_TITLE] = g_param_spec_string ("title", _("Title"), _("The title of the preference"), "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); /** * HdyPreferencesRow:use-underline: * * Whether an embedded underline in the text of the title indicates a * mnemonic. * * Since: 0.0.10 */ props[PROP_USE_UNDERLINE] = g_param_spec_boolean ("use-underline", _("Use underline"), _("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); } static void hdy_preferences_row_init (HdyPreferencesRow *self) { } /** * hdy_preferences_row_new: * * Creates a new #HdyPreferencesRow. * * Returns: a new #HdyPreferencesRow * * Since: 0.0.10 */ HdyPreferencesRow * hdy_preferences_row_new (void) { return g_object_new (HDY_TYPE_PREFERENCES_ROW, NULL); } /** * hdy_preferences_row_get_title: * @self: a #HdyPreferencesRow * * Gets the title of the preference represented by @self. * * Returns: (transfer none) (nullable): the title of the preference represented * by @self, or %NULL. * * Since: 0.0.10 */ const gchar * hdy_preferences_row_get_title (HdyPreferencesRow *self) { HdyPreferencesRowPrivate *priv; g_return_val_if_fail (HDY_IS_PREFERENCES_ROW (self), NULL); priv = hdy_preferences_row_get_instance_private (self); return priv->title; } /** * hdy_preferences_row_set_title: * @self: a #HdyPreferencesRow * @title: (nullable): the title, or %NULL. * * Sets the title of the preference represented by @self. * * Since: 0.0.10 */ void hdy_preferences_row_set_title (HdyPreferencesRow *self, const gchar *title) { HdyPreferencesRowPrivate *priv; g_return_if_fail (HDY_IS_PREFERENCES_ROW (self)); priv = hdy_preferences_row_get_instance_private (self); if (g_strcmp0 (priv->title, title) == 0) return; g_free (priv->title); priv->title = g_strdup (title); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]); } /** * hdy_preferences_row_get_use_underline: * @self: a #HdyPreferencesRow * * Gets whether an embedded underline in the text of the title indicates a * mnemonic. See hdy_preferences_row_set_use_underline(). * * Returns: %TRUE if an embedded underline in the title indicates the mnemonic * accelerator keys. * * Since: 0.0.10 */ gboolean hdy_preferences_row_get_use_underline (HdyPreferencesRow *self) { HdyPreferencesRowPrivate *priv; g_return_val_if_fail (HDY_IS_PREFERENCES_ROW (self), FALSE); priv = hdy_preferences_row_get_instance_private (self); return priv->use_underline; } /** * hdy_preferences_row_set_use_underline: * @self: a #HdyPreferencesRow * @use_underline: %TRUE if underlines in the text indicate mnemonics * * If true, an underline in the text of the title indicates the next character * should be used for the mnemonic accelerator key. * * Since: 0.0.10 */ void hdy_preferences_row_set_use_underline (HdyPreferencesRow *self, gboolean use_underline) { HdyPreferencesRowPrivate *priv; g_return_if_fail (HDY_IS_PREFERENCES_ROW (self)); priv = hdy_preferences_row_get_instance_private (self); if (priv->use_underline == !!use_underline) return; priv->use_underline = !!use_underline; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_USE_UNDERLINE]); } libhandy-0.0.13/src/hdy-preferences-row.h000066400000000000000000000020541360136463700202270ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_PREFERENCES_ROW (hdy_preferences_row_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyPreferencesRow, hdy_preferences_row, HDY, PREFERENCES_ROW, GtkListBoxRow) /** * HdyPreferencesRowClass * @parent_class: The parent class */ struct _HdyPreferencesRowClass { GtkListBoxRowClass parent_class; }; HdyPreferencesRow *hdy_preferences_row_new (void); const gchar *hdy_preferences_row_get_title (HdyPreferencesRow *self); void hdy_preferences_row_set_title (HdyPreferencesRow *self, const gchar *title); gboolean hdy_preferences_row_get_use_underline (HdyPreferencesRow *self); void hdy_preferences_row_set_use_underline (HdyPreferencesRow *self, gboolean use_underline); G_END_DECLS libhandy-0.0.13/src/hdy-preferences-window.c000066400000000000000000000353531360136463700207320ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-preferences-window.h" #include "hdy-action-row.h" #include "hdy-list-box.h" #include "hdy-preferences-group-private.h" #include "hdy-preferences-page-private.h" #include "hdy-squeezer.h" #include "hdy-view-switcher.h" #include "hdy-view-switcher-bar.h" /** * SECTION:hdy-preferences-window * @short_description: A window to present an application's preferences. * @Title: HdyPreferencesWindow * * The #HdyPreferencesWindow widget presents an application's preferences * gathered into pages and groups. The preferences are searchable by the user. * * Since: 0.0.10 */ typedef struct { GtkStack *content_stack; GtkStack *pages_stack; GtkToggleButton *search_button; GtkSearchEntry *search_entry; GtkListBox *search_results; GtkStack *search_stack; HdySqueezer *squeezer; GtkLabel *title_label; GtkStack *title_stack; HdyViewSwitcherBar *view_switcher_bar; HdyViewSwitcher *view_switcher_narrow; HdyViewSwitcher *view_switcher_wide; gint n_last_search_results; } HdyPreferencesWindowPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdyPreferencesWindow, hdy_preferences_window, GTK_TYPE_WINDOW) static gboolean is_title_label_visible (GBinding *binding, const GValue *from_value, GValue *to_value, gpointer user_data) { g_value_set_boolean (to_value, g_value_get_object (from_value) == user_data); return TRUE; } static gboolean filter_search_results (HdyActionRow *row, HdyPreferencesWindow *self) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); g_autofree gchar *text = g_utf8_casefold (gtk_entry_get_text (GTK_ENTRY (priv->search_entry)), -1); g_autofree gchar *title = g_utf8_casefold (hdy_action_row_get_title (row), -1); g_autofree gchar *subtitle = NULL; if (strstr (title, text)) { priv->n_last_search_results++; return TRUE; } subtitle = g_utf8_casefold (hdy_action_row_get_subtitle (row), -1); if (!!strstr (subtitle, text)) { priv->n_last_search_results++; return TRUE; } return FALSE; } static GtkWidget * new_search_row_for_preference (HdyPreferencesRow *row, HdyPreferencesWindow *self) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); HdyActionRow *widget; HdyPreferencesGroup *group; HdyPreferencesPage *page; const gchar *group_title, *page_title; GtkWidget *parent; g_assert (HDY_IS_PREFERENCES_ROW (row)); widget = hdy_action_row_new (); g_object_bind_property (row, "title", widget, "title", G_BINDING_SYNC_CREATE); g_object_bind_property (row, "use-underline", widget, "use-underline", G_BINDING_SYNC_CREATE); for (parent = gtk_widget_get_parent (GTK_WIDGET (row)); parent != NULL && !HDY_IS_PREFERENCES_GROUP (parent); parent = gtk_widget_get_parent (parent)); group = parent != NULL ? HDY_PREFERENCES_GROUP (parent) : NULL; group_title = group != NULL ? hdy_preferences_group_get_title (group) : NULL; if (g_strcmp0 (group_title, "") == 0) group_title = NULL; for (parent = gtk_widget_get_parent (GTK_WIDGET (group)); parent != NULL && !HDY_IS_PREFERENCES_PAGE (parent); parent = gtk_widget_get_parent (parent)); page = parent != NULL ? HDY_PREFERENCES_PAGE (parent) : NULL; page_title = page != NULL ? hdy_preferences_page_get_title (page) : NULL; if (g_strcmp0 (page_title, "") == 0) page_title = NULL; if (group_title && !gtk_widget_get_visible (GTK_WIDGET (priv->view_switcher_wide))) hdy_action_row_set_subtitle (widget, group_title); if (group_title) { g_autofree gchar *subtitle = g_strdup_printf ("%s → %s", page_title != NULL ? page_title : _("Untitled page"), group_title); hdy_action_row_set_subtitle (widget, subtitle); } else if (page_title) hdy_action_row_set_subtitle (widget, page_title); gtk_widget_show (GTK_WIDGET (widget)); g_object_set_data (G_OBJECT (widget), "page", page); g_object_set_data (G_OBJECT (widget), "row", row); return GTK_WIDGET (widget); } static void update_search_results (HdyPreferencesWindow *self) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); g_autoptr (GListStore) model; model = g_list_store_new (HDY_TYPE_PREFERENCES_ROW); gtk_container_foreach (GTK_CONTAINER (priv->pages_stack), (GtkCallback) hdy_preferences_page_add_preferences_to_model, model); gtk_container_foreach (GTK_CONTAINER (priv->search_results), (GtkCallback) gtk_widget_destroy, NULL); for (guint i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (model)); i++) gtk_container_add (GTK_CONTAINER (priv->search_results), new_search_row_for_preference ((HdyPreferencesRow *) g_list_model_get_item (G_LIST_MODEL (model), i), self)); } static void search_result_activated (HdyPreferencesWindow *self, HdyActionRow *widget) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); HdyPreferencesPage *page; HdyPreferencesRow *row; GtkAdjustment *adjustment; GtkAllocation allocation; gint y = 0; gtk_toggle_button_set_active (priv->search_button, FALSE); page = HDY_PREFERENCES_PAGE (g_object_get_data (G_OBJECT (widget), "page")); row = HDY_PREFERENCES_ROW (g_object_get_data (G_OBJECT (widget), "row")); g_assert (page != NULL); g_assert (row != NULL); adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (page)); g_assert (adjustment != NULL); gtk_stack_set_visible_child (priv->pages_stack, GTK_WIDGET (page)); gtk_widget_set_can_focus (GTK_WIDGET (row), TRUE); gtk_widget_grab_focus (GTK_WIDGET (row)); if (!gtk_widget_translate_coordinates (GTK_WIDGET (row), GTK_WIDGET (page), 0, 0, NULL, &y)) return; gtk_container_set_focus_child (GTK_CONTAINER (page), GTK_WIDGET (row)); y += gtk_adjustment_get_value (adjustment); gtk_widget_get_allocation (GTK_WIDGET (row), &allocation); gtk_adjustment_clamp_page (adjustment, y, y + allocation.height); } static gboolean key_pressed (GtkWidget *sender, GdkEvent *event, HdyPreferencesWindow *self) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); GdkModifierType default_modifiers = gtk_accelerator_get_default_mod_mask (); guint keyval; GdkModifierType state; gunichar c; gdk_event_get_keyval (event, &keyval); gdk_event_get_state (event, &state); if ((keyval == GDK_KEY_f || keyval == GDK_KEY_F) && (state & default_modifiers) == GDK_CONTROL_MASK) { gtk_toggle_button_set_active (priv->search_button, TRUE); return TRUE; } if (keyval == GDK_KEY_Escape && gtk_toggle_button_get_active (priv->search_button)) { gtk_toggle_button_set_active (priv->search_button, FALSE); return TRUE; } c = gdk_keyval_to_unicode (keyval); if (g_unichar_isgraph (c)) { gchar text[6] = { 0 }; g_unichar_to_utf8 (c, text); gtk_entry_set_text (GTK_ENTRY (priv->search_entry), text); gtk_toggle_button_set_active (priv->search_button, TRUE); return TRUE; } return FALSE; } static void header_bar_size_allocated (HdyPreferencesWindow *self, GdkRectangle *allocation) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); hdy_squeezer_set_child_enabled (priv->squeezer, GTK_WIDGET (priv->view_switcher_wide), allocation->width > 540); hdy_squeezer_set_child_enabled (priv->squeezer, GTK_WIDGET (priv->view_switcher_narrow), allocation->width > 360); } static void search_button_activated (HdyPreferencesWindow *self) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); if (gtk_toggle_button_get_active (priv->search_button)) { update_search_results (self); gtk_stack_set_visible_child_name (priv->title_stack, "search"); gtk_stack_set_visible_child_name (priv->content_stack, "search"); gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->search_entry)); /* Grabbing without selecting puts the cursor at the start of the buffer, so * for "type to search" to work we must move the cursor at the end. We can't * use GTK_MOVEMENT_BUFFER_ENDS because it causes a sound to be played. */ g_signal_emit_by_name (priv->search_entry, "move-cursor", GTK_MOVEMENT_LOGICAL_POSITIONS, G_MAXINT, FALSE, NULL); } else { gtk_stack_set_visible_child_name (priv->title_stack, "pages"); gtk_stack_set_visible_child_name (priv->content_stack, "pages"); } } static void search_changed (HdyPreferencesWindow *self) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); priv->n_last_search_results = 0; gtk_list_box_invalidate_filter (priv->search_results); gtk_stack_set_visible_child_name (priv->search_stack, priv->n_last_search_results > 0 ? "results" : "no-results"); } static void count_children_cb (GtkWidget *widget, gint *count) { (*count)++; } static void update_pages_switcher_visibility (HdyPreferencesWindow *self) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); gint count = 0; gtk_container_foreach (GTK_CONTAINER (priv->pages_stack), (GtkCallback) count_children_cb, &count); gtk_widget_set_visible (GTK_WIDGET (priv->view_switcher_wide), count > 1); gtk_widget_set_visible (GTK_WIDGET (priv->view_switcher_narrow), count > 1); gtk_widget_set_visible (GTK_WIDGET (priv->view_switcher_bar), count > 1); } static void on_page_icon_name_changed (HdyPreferencesPage *page, GParamSpec *pspec, HdyPreferencesWindow *self) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); gtk_container_child_set (GTK_CONTAINER (priv->pages_stack), GTK_WIDGET (page), "icon-name", hdy_preferences_page_get_icon_name (page), NULL); } static void on_page_title_changed (HdyPreferencesPage *page, GParamSpec *pspec, HdyPreferencesWindow *self) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); gtk_container_child_set (GTK_CONTAINER (priv->pages_stack), GTK_WIDGET (page), "title", hdy_preferences_page_get_title (page), NULL); } static void hdy_preferences_window_add (GtkContainer *container, GtkWidget *child) { HdyPreferencesWindow *self = HDY_PREFERENCES_WINDOW (container); HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); if (priv->content_stack == NULL) GTK_CONTAINER_CLASS (hdy_preferences_window_parent_class)->add (container, child); else if (HDY_IS_PREFERENCES_PAGE (child)) { gtk_container_add (GTK_CONTAINER (priv->pages_stack), child); on_page_icon_name_changed (HDY_PREFERENCES_PAGE (child), NULL, self); on_page_title_changed (HDY_PREFERENCES_PAGE (child), NULL, self); g_signal_connect (child, "notify::icon-name", G_CALLBACK (on_page_icon_name_changed), self); g_signal_connect (child, "notify::title", G_CALLBACK (on_page_title_changed), self); update_pages_switcher_visibility (self); } else g_warning ("Can't add children of type %s to %s", G_OBJECT_TYPE_NAME (child), G_OBJECT_TYPE_NAME (container)); } static void hdy_preferences_window_class_init (HdyPreferencesWindowClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); container_class->add = hdy_preferences_window_add; gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-preferences-window.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, content_stack); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, pages_stack); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, search_button); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, search_entry); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, search_results); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, search_stack); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, squeezer); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, title_label); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, title_stack); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, view_switcher_bar); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, view_switcher_narrow); gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesWindow, view_switcher_wide); gtk_widget_class_bind_template_callback (widget_class, header_bar_size_allocated); gtk_widget_class_bind_template_callback (widget_class, key_pressed); gtk_widget_class_bind_template_callback (widget_class, search_button_activated); gtk_widget_class_bind_template_callback (widget_class, search_changed); gtk_widget_class_bind_template_callback (widget_class, search_result_activated); } static void hdy_preferences_window_init (HdyPreferencesWindow *self) { HdyPreferencesWindowPrivate *priv = hdy_preferences_window_get_instance_private (self); gtk_widget_init_template (GTK_WIDGET (self)); g_object_bind_property_full (priv->squeezer, "visible-child", priv->view_switcher_bar, "reveal", G_BINDING_SYNC_CREATE, is_title_label_visible, NULL, priv->title_label, NULL); gtk_list_box_set_header_func (priv->search_results, hdy_list_box_separator_header, NULL, NULL); gtk_list_box_set_filter_func (priv->search_results, (GtkListBoxFilterFunc) filter_search_results, self, NULL); update_pages_switcher_visibility (self); } /** * hdy_preferences_window_new: * * Creates a new #HdyPreferencesWindow. * * Returns: a new #HdyPreferencesWindow * * Since: 0.0.10 */ HdyPreferencesWindow * hdy_preferences_window_new (void) { return g_object_new (HDY_TYPE_PREFERENCES_WINDOW, NULL); } libhandy-0.0.13/src/hdy-preferences-window.h000066400000000000000000000012051360136463700207240ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_PREFERENCES_WINDOW (hdy_preferences_window_get_type()) G_DECLARE_DERIVABLE_TYPE (HdyPreferencesWindow, hdy_preferences_window, HDY, PREFERENCES_WINDOW, GtkWindow) /** * HdyPreferencesWindowClass * @parent_class: The parent class */ struct _HdyPreferencesWindowClass { GtkWindowClass parent_class; }; HdyPreferencesWindow *hdy_preferences_window_new (void); G_END_DECLS libhandy-0.0.13/src/hdy-preferences-window.ui000066400000000000000000000272161360136463700211240ustar00rootroot00000000000000 libhandy-0.0.13/src/hdy-search-bar.c000066400000000000000000000477101360136463700171330ustar00rootroot00000000000000/* GTK - The GIMP Toolkit * Copyright (C) 2013 Red Hat, Inc. * Copyright (C) 2018 Purism SPC * * Authors: * - Bastien Nocera * - Adrien Plazas * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * SPDX-License-Identifier: LGPL-2.1+ */ /* * Modified by the GTK+ Team and others 2013. See the AUTHORS * file for a list of people on the GTK Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK at ftp://ftp.gtk.org/pub/gtk/. */ /* * Forked from the GTK+ 3.94.0 GtkSearchBar widget and modified for libhandy by * Adrien Plazas on behalf of Purism SPC 2018. * * The AUTHORS file referenced above is part of GTK and not present in * libhandy. At the time of the fork it was available here: * https://gitlab.gnome.org/GNOME/gtk/blob/faba0f0145b1281facba20fb90699e3db594fbb0/AUTHORS * * The ChangeLog file referenced above was not present in GTK+ at the time of * the fork. */ #include "config.h" #include #include "hdy-search-bar.h" /** * SECTION:hdy-search-bar * @short_description: A toolbar to integrate a search entry with. * @Title: HdySearchBar * * #HdySearchBar is a container made to have a search entry (possibly * with additional connex widgets, such as drop-down menus, or buttons) * built-in. The search bar would appear when a search is started through * typing on the keyboard, or the application’s search mode is toggled on. * * For keyboard presses to start a search, events will need to be * forwarded from the top-level window that contains the search bar. * See hdy_search_bar_handle_event() for example code. Common shortcuts * such as Ctrl+F should be handled as an application action, or through * the menu items. * * You will also need to tell the search bar about which entry you * are using as your search entry using hdy_search_bar_connect_entry(). * The following example shows you how to create a more complex search * entry. * * HdySearchBar is very similar to #GtkSearchBar, the main difference being that * it allows the search entry to fill all the available space. This allows you * to control your search entry's width with a #HdyColumn. * * # CSS nodes * * HdySearchBar has a single CSS node with name searchbar. * * Since: 0.0.6 */ typedef struct { /* Template widgets */ GtkWidget *revealer; GtkWidget *tool_box; GtkWidget *start; GtkWidget *end; GtkWidget *close_button; GtkWidget *entry; gboolean reveal_child; gboolean show_close_button; } HdySearchBarPrivate; G_DEFINE_TYPE_WITH_PRIVATE (HdySearchBar, hdy_search_bar, GTK_TYPE_BIN) enum { PROP_0, PROP_SEARCH_MODE_ENABLED, PROP_SHOW_CLOSE_BUTTON, LAST_PROPERTY }; static GParamSpec *props[LAST_PROPERTY] = { NULL, }; /* This comes from gtksearchentry.c in GTK. */ static gboolean gtk_search_entry_is_keynav_event (GdkEvent *event) { GdkModifierType state = 0; guint keyval; if (!gdk_event_get_keyval (event, &keyval)) return FALSE; gdk_event_get_state (event, &state); if (keyval == GDK_KEY_Tab || keyval == GDK_KEY_KP_Tab || keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up || keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down || keyval == GDK_KEY_Left || keyval == GDK_KEY_KP_Left || keyval == GDK_KEY_Right || keyval == GDK_KEY_KP_Right || keyval == GDK_KEY_Home || keyval == GDK_KEY_KP_Home || keyval == GDK_KEY_End || keyval == GDK_KEY_KP_End || keyval == GDK_KEY_Page_Up || keyval == GDK_KEY_KP_Page_Up || keyval == GDK_KEY_Page_Down || keyval == GDK_KEY_KP_Page_Down || ((state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) != 0)) return TRUE; /* Other navigation events should get automatically * ignored as they will not change the content of the entry */ return FALSE; } static void stop_search_cb (GtkWidget *entry, HdySearchBar *self) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); gtk_revealer_set_reveal_child (GTK_REVEALER (priv->revealer), FALSE); } static gboolean entry_key_pressed_event_cb (GtkWidget *widget, GdkEvent *event, HdySearchBar *self) { if (event->key.keyval == GDK_KEY_Escape) { stop_search_cb (widget, self); return GDK_EVENT_STOP; } else { return GDK_EVENT_PROPAGATE; } } static void preedit_changed_cb (GtkEntry *entry, GtkWidget *popup, gboolean *preedit_changed) { *preedit_changed = TRUE; } static gboolean hdy_search_bar_handle_event_for_entry (HdySearchBar *self, GdkEvent *event) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); gboolean handled; gboolean preedit_changed; guint preedit_change_id; gboolean res; char *old_text, *new_text; if (gtk_search_entry_is_keynav_event (event) || event->key.keyval == GDK_KEY_space || event->key.keyval == GDK_KEY_Menu) return GDK_EVENT_PROPAGATE; if (!gtk_widget_get_realized (priv->entry)) gtk_widget_realize (priv->entry); handled = GDK_EVENT_PROPAGATE; preedit_changed = FALSE; preedit_change_id = g_signal_connect (priv->entry, "preedit-changed", G_CALLBACK (preedit_changed_cb), &preedit_changed); old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->entry))); res = gtk_widget_event (priv->entry, event); new_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->entry))); g_signal_handler_disconnect (priv->entry, preedit_change_id); if ((res && g_strcmp0 (new_text, old_text) != 0) || preedit_changed) handled = GDK_EVENT_STOP; g_free (old_text); g_free (new_text); return handled; } /** * hdy_search_bar_handle_event: * @self: a #HdySearchBar * @event: a #GdkEvent containing key press events * * This function should be called when the top-level * window which contains the search bar received a key event. * * If the key event is handled by the search bar, the bar will * be shown, the entry populated with the entered text and %GDK_EVENT_STOP * will be returned. The caller should ensure that events are * not propagated further. * * If no entry has been connected to the search bar, using * hdy_search_bar_connect_entry(), this function will return * immediately with a warning. * * ## Showing the search bar on key presses * * |[ * static gboolean * on_key_press_event (GtkWidget *widget, * GdkEvent *event, * gpointer user_data) * { * HdySearchBar *bar = HDY_SEARCH_BAR (user_data); * return hdy_search_bar_handle_event (self, event); * } * * static void * create_toplevel (void) * { * GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL); * GtkWindow *search_bar = hdy_search_bar_new (); * * // Add more widgets to the window... * * g_signal_connect (window, * "key-press-event", * G_CALLBACK (on_key_press_event), * search_bar); * } * ]| * * Returns: %GDK_EVENT_STOP if the key press event resulted * in text being entered in the search entry (and revealing * the search bar if necessary), %GDK_EVENT_PROPAGATE otherwise. * * Since: 0.0.6 */ gboolean hdy_search_bar_handle_event (HdySearchBar *self, GdkEvent *event) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); gboolean handled; if (priv->reveal_child) return GDK_EVENT_PROPAGATE; if (priv->entry == NULL) { g_warning ("The search bar does not have an entry connected to it. Call hdy_search_bar_connect_entry() to connect one."); return GDK_EVENT_PROPAGATE; } if (GTK_IS_SEARCH_ENTRY (priv->entry)) handled = gtk_search_entry_handle_event (GTK_SEARCH_ENTRY (priv->entry), event); else handled = hdy_search_bar_handle_event_for_entry (self, event); if (handled == GDK_EVENT_STOP) gtk_revealer_set_reveal_child (GTK_REVEALER (priv->revealer), TRUE); return handled; } static void reveal_child_changed_cb (GObject *object, GParamSpec *pspec, HdySearchBar *self) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); gboolean reveal_child; g_object_get (object, "reveal-child", &reveal_child, NULL); if (reveal_child) gtk_widget_set_child_visible (priv->revealer, TRUE); if (reveal_child == priv->reveal_child) return; priv->reveal_child = reveal_child; if (priv->entry) { if (reveal_child) gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->entry)); else gtk_entry_set_text (GTK_ENTRY (priv->entry), ""); } g_object_notify (G_OBJECT (self), "search-mode-enabled"); } static void child_revealed_changed_cb (GObject *object, GParamSpec *pspec, HdySearchBar *self) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); gboolean val; g_object_get (object, "child-revealed", &val, NULL); if (!val) gtk_widget_set_child_visible (priv->revealer, FALSE); } static void close_button_clicked_cb (GtkWidget *button, HdySearchBar *self) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); gtk_revealer_set_reveal_child (GTK_REVEALER (priv->revealer), FALSE); } static void hdy_search_bar_add (GtkContainer *container, GtkWidget *child) { HdySearchBar *self = HDY_SEARCH_BAR (container); HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); /* When constructing the widget, we want the revealer to be added * as the first child of the search bar, as an implementation detail. * After that, the child added by the application should be added * to column. */ if (priv->tool_box == NULL) { GTK_CONTAINER_CLASS (hdy_search_bar_parent_class)->add (container, child); } else { gtk_box_set_center_widget (GTK_BOX (priv->tool_box), child); gtk_container_child_set (GTK_CONTAINER (priv->tool_box), child, "expand", TRUE, NULL); /* If an entry is the only child, save the developer a couple of * lines of code */ if (GTK_IS_ENTRY (child)) hdy_search_bar_connect_entry (self, GTK_ENTRY (child)); } } static void hdy_search_bar_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdySearchBar *self = HDY_SEARCH_BAR (object); switch (prop_id) { case PROP_SEARCH_MODE_ENABLED: hdy_search_bar_set_search_mode (self, g_value_get_boolean (value)); break; case PROP_SHOW_CLOSE_BUTTON: hdy_search_bar_set_show_close_button (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void hdy_search_bar_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdySearchBar *self = HDY_SEARCH_BAR (object); HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); switch (prop_id) { case PROP_SEARCH_MODE_ENABLED: g_value_set_boolean (value, priv->reveal_child); break; case PROP_SHOW_CLOSE_BUTTON: g_value_set_boolean (value, hdy_search_bar_get_show_close_button (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void hdy_search_bar_set_entry (HdySearchBar *self, GtkEntry *entry); static void hdy_search_bar_dispose (GObject *object) { HdySearchBar *self = HDY_SEARCH_BAR (object); hdy_search_bar_set_entry (self, NULL); G_OBJECT_CLASS (hdy_search_bar_parent_class)->dispose (object); } static gboolean hdy_search_bar_draw (GtkWidget *widget, cairo_t *cr) { gint width, height; GtkStyleContext *context; width = gtk_widget_get_allocated_width (widget); height = gtk_widget_get_allocated_height (widget); context = gtk_widget_get_style_context (widget); gtk_render_background (context, cr, 0, 0, width, height); gtk_render_frame (context, cr, 0, 0, width, height); GTK_WIDGET_CLASS (hdy_search_bar_parent_class)->draw (widget, cr); return FALSE; } static void hdy_search_bar_class_init (HdySearchBarClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->dispose = hdy_search_bar_dispose; object_class->set_property = hdy_search_bar_set_property; object_class->get_property = hdy_search_bar_get_property; widget_class->draw = hdy_search_bar_draw; container_class->add = hdy_search_bar_add; /** * HdySearchBar:search-mode-enabled: * * Whether the search mode is on and the search bar shown. * * See hdy_search_bar_set_search_mode() for details. */ props[PROP_SEARCH_MODE_ENABLED] = g_param_spec_boolean ("search-mode-enabled", _("Search Mode Enabled"), _("Whether the search mode is on and the search bar shown"), FALSE, G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); /** * HdySearchBar:show-close-button: * * Whether to show the close button in the toolbar. */ props[PROP_SHOW_CLOSE_BUTTON] = g_param_spec_boolean ("show-close-button", _("Show Close Button"), _("Whether to show the close button in the toolbar"), FALSE, G_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROPERTY, props); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-search-bar.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdySearchBar, tool_box); gtk_widget_class_bind_template_child_private (widget_class, HdySearchBar, revealer); gtk_widget_class_bind_template_child_private (widget_class, HdySearchBar, start); gtk_widget_class_bind_template_child_private (widget_class, HdySearchBar, end); gtk_widget_class_bind_template_child_private (widget_class, HdySearchBar, close_button); gtk_widget_class_set_css_name (widget_class, "searchbar"); } static void hdy_search_bar_init (HdySearchBar *self) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); gtk_widget_init_template (GTK_WIDGET (self)); /* We use child-visible to avoid the unexpanded revealer * peaking out by 1 pixel */ gtk_widget_set_child_visible (priv->revealer, FALSE); g_signal_connect (priv->revealer, "notify::reveal-child", G_CALLBACK (reveal_child_changed_cb), self); g_signal_connect (priv->revealer, "notify::child-revealed", G_CALLBACK (child_revealed_changed_cb), self); gtk_widget_set_no_show_all (priv->start, TRUE); gtk_widget_set_no_show_all (priv->end, TRUE); g_signal_connect (priv->close_button, "clicked", G_CALLBACK (close_button_clicked_cb), self); }; /** * hdy_search_bar_new: * * Creates a #HdySearchBar. You will need to tell it about * which widget is going to be your text entry using * hdy_search_bar_connect_entry(). * * Returns: a new #HdySearchBar * * Since: 0.0.6 */ GtkWidget * hdy_search_bar_new (void) { return g_object_new (HDY_TYPE_SEARCH_BAR, NULL); } static void hdy_search_bar_set_entry (HdySearchBar *self, GtkEntry *entry) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); if (priv->entry != NULL) { if (GTK_IS_SEARCH_ENTRY (priv->entry)) g_signal_handlers_disconnect_by_func (priv->entry, stop_search_cb, self); else g_signal_handlers_disconnect_by_func (priv->entry, entry_key_pressed_event_cb, self); g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *) &priv->entry); } priv->entry = GTK_WIDGET (entry); if (priv->entry != NULL) { g_object_add_weak_pointer (G_OBJECT (priv->entry), (gpointer *) &priv->entry); if (GTK_IS_SEARCH_ENTRY (priv->entry)) g_signal_connect (priv->entry, "stop-search", G_CALLBACK (stop_search_cb), self); else g_signal_connect (priv->entry, "key-press-event", G_CALLBACK (entry_key_pressed_event_cb), self); } } /** * hdy_search_bar_connect_entry: * @self: a #HdySearchBar * @entry: a #GtkEntry * * Connects the #GtkEntry widget passed as the one to be used in * this search bar. The entry should be a descendant of the search bar. * This is only required if the entry isn’t the direct child of the * search bar (as in our main example). * * Since: 0.0.6 */ void hdy_search_bar_connect_entry (HdySearchBar *self, GtkEntry *entry) { g_return_if_fail (HDY_IS_SEARCH_BAR (self)); g_return_if_fail (entry == NULL || GTK_IS_ENTRY (entry)); hdy_search_bar_set_entry (self, entry); } /** * hdy_search_bar_get_search_mode: * @self: a #HdySearchBar * * Returns whether the search mode is on or off. * * Returns: whether search mode is toggled on * * Since: 0.0.6 */ gboolean hdy_search_bar_get_search_mode (HdySearchBar *self) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); g_return_val_if_fail (HDY_IS_SEARCH_BAR (self), FALSE); return priv->reveal_child; } /** * hdy_search_bar_set_search_mode: * @self: a #HdySearchBar * @search_mode: the new state of the search mode * * Switches the search mode on or off. * * Since: 0.0.6 */ void hdy_search_bar_set_search_mode (HdySearchBar *self, gboolean search_mode) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); g_return_if_fail (HDY_IS_SEARCH_BAR (self)); gtk_revealer_set_reveal_child (GTK_REVEALER (priv->revealer), search_mode); } /** * hdy_search_bar_get_show_close_button: * @self: a #HdySearchBar * * Returns whether the close button is shown. * * Returns: whether the close button is shown * * Since: 0.0.6 */ gboolean hdy_search_bar_get_show_close_button (HdySearchBar *self) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); g_return_val_if_fail (HDY_IS_SEARCH_BAR (self), FALSE); return priv->show_close_button; } /** * hdy_search_bar_set_show_close_button: * @self: a #HdySearchBar * @visible: whether the close button will be shown or not * * Shows or hides the close button. Applications that * already have a “search” toggle button should not show a close * button in their search bar, as it duplicates the role of the * toggle button. * * Since: 0.0.6 */ void hdy_search_bar_set_show_close_button (HdySearchBar *self, gboolean visible) { HdySearchBarPrivate *priv = hdy_search_bar_get_instance_private (self); g_return_if_fail (HDY_IS_SEARCH_BAR (self)); visible = visible != FALSE; if (priv->show_close_button == visible) return; priv->show_close_button = visible; gtk_widget_set_visible (priv->start, visible); gtk_widget_set_visible (priv->end, visible); g_object_notify (G_OBJECT (self), "show-close-button"); } libhandy-0.0.13/src/hdy-search-bar.h000066400000000000000000000024421360136463700171310ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_SEARCH_BAR (hdy_search_bar_get_type()) G_DECLARE_DERIVABLE_TYPE (HdySearchBar, hdy_search_bar, HDY, SEARCH_BAR, GtkBin) struct _HdySearchBarClass { GtkBinClass parent_class; }; GtkWidget *hdy_search_bar_new (void); void hdy_search_bar_connect_entry (HdySearchBar *self, GtkEntry *entry); gboolean hdy_search_bar_get_search_mode (HdySearchBar *self); void hdy_search_bar_set_search_mode (HdySearchBar *self, gboolean search_mode); gboolean hdy_search_bar_get_show_close_button (HdySearchBar *self); void hdy_search_bar_set_show_close_button (HdySearchBar *self, gboolean visible); gboolean hdy_search_bar_handle_event (HdySearchBar *self, GdkEvent *event); G_END_DECLS libhandy-0.0.13/src/hdy-search-bar.ui000066400000000000000000000045631360136463700173250ustar00rootroot00000000000000 horizontal libhandy-0.0.13/src/hdy-shadow-helper-private.h000066400000000000000000000020731360136463700213340ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_SHADOW_HELPER (hdy_shadow_helper_get_type()) G_DECLARE_FINAL_TYPE (HdyShadowHelper, hdy_shadow_helper, HDY, SHADOW_HELPER, GObject) HdyShadowHelper *hdy_shadow_helper_new (GtkWidget *widget, const gchar *css_path); void hdy_shadow_helper_clear_cache (HdyShadowHelper *self); void hdy_shadow_helper_draw_shadow (HdyShadowHelper *self, cairo_t *cr, gint width, gint height, gdouble progress, GtkPanDirection direction); G_END_DECLS libhandy-0.0.13/src/hdy-shadow-helper.c000066400000000000000000000300441360136463700176560ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-shadow-helper-private.h" #include "hdy-style-private.h" #include /** * PRIVATE:hdy-shadow-helper * @short_description: Shadow helper used in #HdyLeaflet * @title: HdyShadowHelper * @See_also: #HdyLeaflet * @stability: Private * * A helper class for drawing #HdyLeaflet transition shadow. * * Since: 0.0.12 */ struct _HdyShadowHelper { GObject parent_instance; GtkWidget *widget; gchar *css_path; GtkCssProvider *provider; gboolean is_cache_valid; cairo_pattern_t *dimming_pattern; cairo_pattern_t *shadow_pattern; cairo_pattern_t *border_pattern; gint shadow_size; gint border_size; GtkPanDirection last_direction; gint last_width; gint last_height; gint last_scale; }; G_DEFINE_TYPE (HdyShadowHelper, hdy_shadow_helper, G_TYPE_OBJECT); enum { PROP_0, PROP_WIDGET, PROP_CSS_PATH, LAST_PROP, }; static GParamSpec *props[LAST_PROP]; static GtkStyleContext * create_context (HdyShadowHelper *self, const gchar *name, GtkPanDirection direction) { g_autoptr(GtkWidgetPath) path = NULL; GtkStyleContext *context; gint pos; const gchar *direction_name; GEnumClass *enum_class; enum_class = g_type_class_ref (GTK_TYPE_PAN_DIRECTION); direction_name = g_enum_get_value (enum_class, direction)->value_nick; path = gtk_widget_path_copy (gtk_widget_get_path (self->widget)); pos = gtk_widget_path_append_type (path, GTK_TYPE_WIDGET); gtk_widget_path_iter_set_object_name (path, pos, name); gtk_widget_path_iter_add_class (path, pos, direction_name); context = gtk_style_context_new (); gtk_style_context_set_path (context, path); gtk_style_context_set_parent (context, gtk_widget_get_style_context (self->widget)); gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (self->provider), HDY_STYLE_PROVIDER_PRIORITY); g_type_class_unref (enum_class); return context; } static gint get_element_size (GtkStyleContext *context, GtkPanDirection direction) { gint width, height; gtk_style_context_get (context, gtk_style_context_get_state (context), "min-width", &width, "min-height", &height, NULL); switch (direction) { case GTK_PAN_DIRECTION_LEFT: case GTK_PAN_DIRECTION_RIGHT: return width; case GTK_PAN_DIRECTION_UP: case GTK_PAN_DIRECTION_DOWN: return height; default: g_assert_not_reached (); } return 0; } static cairo_pattern_t * create_element_pattern (GtkStyleContext *context, gint width, gint height) { cairo_surface_t *surface; cairo_t *cr; cairo_pattern_t *pattern; surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); cr = cairo_create (surface); gtk_render_background (context, cr, 0, 0, width, height); gtk_render_frame (context, cr, 0, 0, width, height); pattern = cairo_pattern_create_for_surface (surface); cairo_destroy (cr); cairo_surface_destroy (surface); return pattern; } static void cache_shadow (HdyShadowHelper *self, gint width, gint height, GtkPanDirection direction) { g_autoptr(GtkStyleContext) dim_context = NULL; g_autoptr(GtkStyleContext) shadow_context = NULL; g_autoptr(GtkStyleContext) border_context = NULL; gint shadow_size, border_size, scale; scale = gtk_widget_get_scale_factor (self->widget); if (self->last_direction == direction && self->last_width == width && self->last_height == height && self->last_scale == scale && self->is_cache_valid) return; hdy_shadow_helper_clear_cache (self); dim_context = create_context (self, "dimming", direction); shadow_context = create_context (self, "shadow", direction); border_context = create_context (self, "border", direction); shadow_size = get_element_size (shadow_context, direction); border_size = get_element_size (border_context, direction); self->dimming_pattern = create_element_pattern (dim_context, width, height); if (direction == GTK_PAN_DIRECTION_LEFT || direction == GTK_PAN_DIRECTION_RIGHT) { self->shadow_pattern = create_element_pattern (shadow_context, shadow_size, height); self->border_pattern = create_element_pattern (border_context, border_size, height); } else { self->shadow_pattern = create_element_pattern (shadow_context, width, shadow_size); self->border_pattern = create_element_pattern (border_context, width, border_size); } self->border_size = border_size; self->shadow_size = shadow_size; self->is_cache_valid = TRUE; self->last_direction = direction; self->last_width = width; self->last_height = height; self->last_scale = scale; } static void hdy_shadow_helper_constructed (GObject *object) { HdyShadowHelper *self = HDY_SHADOW_HELPER (object); self->provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (self->provider, "/sm/puri/handy/style/hdy-leaflet.css"); G_OBJECT_CLASS (hdy_shadow_helper_parent_class)->constructed (object); } static void hdy_shadow_helper_dispose (GObject *object) { HdyShadowHelper *self = HDY_SHADOW_HELPER (object); hdy_shadow_helper_clear_cache (self); if (self->widget) g_clear_object (&self->widget); G_OBJECT_CLASS (hdy_shadow_helper_parent_class)->dispose (object); } static void hdy_shadow_helper_finalize (GObject *object) { HdyShadowHelper *self = HDY_SHADOW_HELPER (object); if (self->css_path) g_free (self->css_path); g_object_unref (self->provider); G_OBJECT_CLASS (hdy_shadow_helper_parent_class)->finalize (object); } static void hdy_shadow_helper_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyShadowHelper *self = HDY_SHADOW_HELPER (object); switch (prop_id) { case PROP_WIDGET: g_value_set_object (value, self->widget); break; case PROP_CSS_PATH: g_value_set_string (value, self->css_path); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_shadow_helper_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyShadowHelper *self = HDY_SHADOW_HELPER (object); switch (prop_id) { case PROP_WIDGET: self->widget = GTK_WIDGET (g_object_ref (g_value_get_object (value))); break; case PROP_CSS_PATH: if (self->css_path) g_clear_pointer (&self->css_path, g_free); self->css_path = g_strdup (g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_shadow_helper_class_init (HdyShadowHelperClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = hdy_shadow_helper_constructed; object_class->dispose = hdy_shadow_helper_dispose; object_class->finalize = hdy_shadow_helper_finalize; object_class->get_property = hdy_shadow_helper_get_property; object_class->set_property = hdy_shadow_helper_set_property; /** * HdyShadowHelper:widget: * * The widget the shadow will be drawn for. Must not be %NULL * * Since: 0.0.11 */ props[PROP_WIDGET] = g_param_spec_object ("widget", _("Widget"), _("The widget the shadow will be drawn for"), GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * HdyShadowHelper:css-path: * * The CSS resource path to be used for the shadow. Must not be %NULL. * * Since: 0.0.11 */ props[PROP_CSS_PATH] = g_param_spec_string ("css-path", _("CSS Path"), _("The CSS resource path to be used for the shadow"), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_properties (object_class, LAST_PROP, props); } static void hdy_shadow_helper_init (HdyShadowHelper *self) { } /** * hdy_shadow_helper_new: * * Creates a new #HdyShadowHelper object. * * Returns: The newly created #HdyShadowHelper object * * Since: 0.0.12 */ HdyShadowHelper * hdy_shadow_helper_new (GtkWidget *widget, const gchar *css_path) { return g_object_new (HDY_TYPE_SHADOW_HELPER, "widget", widget, "css-path", css_path, NULL); } /** * hdy_shadow_helper_clear_cache: * @self: a #HdyShadowHelper * * Clears shadow cache. This should be used after a transition is done. * * Since: 0.0.12 */ void hdy_shadow_helper_clear_cache (HdyShadowHelper *self) { if (!self->is_cache_valid) return; cairo_pattern_destroy (self->dimming_pattern); cairo_pattern_destroy (self->shadow_pattern); cairo_pattern_destroy (self->border_pattern); self->border_size = 0; self->shadow_size = 0; self->last_direction = 0; self->last_width = 0; self->last_height = 0; self->last_scale = 0; self->is_cache_valid = FALSE; } /** * hdy_shadow_helper_draw_shadow: * @self: a #HdyShadowHelper * @cr: a Cairo context to draw to * @width: the width of the shadow rectangle * @height: the height of the shadow rectangle * @progress: transition progress, changes from 0 to 1 * @direction: shadow direction * * Draws a transition shadow. For caching to work, @width, @height and * @direction shouldn't change between calls. * * Since: 0.0.12 */ void hdy_shadow_helper_draw_shadow (HdyShadowHelper *self, cairo_t *cr, gint width, gint height, gdouble progress, GtkPanDirection direction) { gdouble remaining_distance, shadow_opacity; gint shadow_size, border_size, distance; cache_shadow (self, width, height, direction); shadow_size = self->shadow_size; border_size = self->border_size; switch (direction) { case GTK_PAN_DIRECTION_LEFT: case GTK_PAN_DIRECTION_RIGHT: distance = width; break; case GTK_PAN_DIRECTION_UP: case GTK_PAN_DIRECTION_DOWN: distance = height; break; default: g_assert_not_reached (); } remaining_distance = (1 - progress) * (gdouble) distance; shadow_opacity = 1; if (remaining_distance < shadow_size) shadow_opacity = (remaining_distance / shadow_size); cairo_save (cr); cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_ATOP); cairo_set_source (cr, self->dimming_pattern); cairo_paint_with_alpha (cr, 1 - progress); cairo_restore (cr); switch (direction) { case GTK_PAN_DIRECTION_RIGHT: cairo_translate (cr, width - shadow_size, 0); break; case GTK_PAN_DIRECTION_DOWN: cairo_translate (cr, 0, height - shadow_size); break; case GTK_PAN_DIRECTION_LEFT: case GTK_PAN_DIRECTION_UP: break; default: g_assert_not_reached (); } cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_ATOP); cairo_set_source (cr, self->shadow_pattern); cairo_paint_with_alpha (cr, shadow_opacity); cairo_restore (cr); switch (direction) { case GTK_PAN_DIRECTION_RIGHT: cairo_translate (cr, shadow_size - border_size, 0); break; case GTK_PAN_DIRECTION_DOWN: cairo_translate (cr, 0, shadow_size - border_size); break; case GTK_PAN_DIRECTION_LEFT: case GTK_PAN_DIRECTION_UP: break; default: g_assert_not_reached (); } cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_ATOP); cairo_set_source (cr, self->border_pattern); cairo_paint (cr); cairo_restore (cr); cairo_restore (cr); } libhandy-0.0.13/src/hdy-squeezer.c000066400000000000000000001313431360136463700167630ustar00rootroot00000000000000/* * Copyright (C) 2013 Red Hat, Inc. * Copyright (C) 2019 Purism SPC * * Author: Alexander Larsson * Author: Adrien Plazas * * SPDX-License-Identifier: LGPL-2.1+ */ /* * Forked from the GTK+ 3.24.2 GtkStack widget initially written by Alexander * Larsson, and heavily modified for libhandy by Adrien Plazas on behalf of * Purism SPC 2019. */ #include "config.h" #include #include "hdy-squeezer.h" #include "gtkprogresstrackerprivate.h" #include "hdy-animation-private.h" /** * SECTION:hdy-squeezer * @short_description: A best fit container. * @Title: HdySqueezer * * The HdySqueezer widget is a container which only shows the first of its * children that fits in the available size. It is convenient to offer different * widgets to represent the same data with different levels of detail, making * the widget seem to squeeze itself to fit in the available space. * * Transitions between children can be animated as fades. This can be controlled * with hdy_squeezer_set_transition_type(). */ /** * HdySqueezerTransitionType: * @HDY_SQUEEZER_TRANSITION_TYPE_NONE: No transition * @HDY_SQUEEZER_TRANSITION_TYPE_CROSSFADE: A cross-fade * * These enumeration values describe the possible transitions between children * in a #HdySqueezer widget. */ enum { PROP_0, PROP_HOMOGENEOUS, PROP_VISIBLE_CHILD, PROP_TRANSITION_DURATION, PROP_TRANSITION_TYPE, PROP_TRANSITION_RUNNING, PROP_INTERPOLATE_SIZE, /* Overridden properties */ PROP_ORIENTATION, LAST_PROP = PROP_INTERPOLATE_SIZE + 1, }; enum { CHILD_PROP_0, CHILD_PROP_ENABLED, LAST_CHILD_PROP, }; typedef struct { GtkWidget *widget; gboolean enabled; GtkWidget *last_focus; } HdySqueezerChildInfo; typedef struct { GList *children; GdkWindow* bin_window; GdkWindow* view_window; HdySqueezerChildInfo *visible_child; gboolean homogeneous; HdySqueezerTransitionType transition_type; guint transition_duration; HdySqueezerChildInfo *last_visible_child; cairo_surface_t *last_visible_surface; GtkAllocation last_visible_surface_allocation; guint tick_id; GtkProgressTracker tracker; gboolean first_frame_skipped; gint last_visible_widget_width; gint last_visible_widget_height; HdySqueezerTransitionType active_transition_type; gboolean interpolate_size; GtkOrientation orientation; } HdySqueezerPrivate; static GParamSpec *props[LAST_PROP]; static GParamSpec *child_props[LAST_CHILD_PROP]; G_DEFINE_TYPE_WITH_CODE (HdySqueezer, hdy_squeezer, GTK_TYPE_CONTAINER, G_ADD_PRIVATE (HdySqueezer) G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)) static GtkOrientation get_orientation (HdySqueezer *self) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); return priv->orientation; } static void set_orientation (HdySqueezer *self, GtkOrientation orientation) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); if (priv->orientation == orientation) return; priv->orientation = orientation; gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify (G_OBJECT (self), "orientation"); } static HdySqueezerChildInfo * find_child_info_for_widget (HdySqueezer *self, GtkWidget *child) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); HdySqueezerChildInfo *info; GList *l; for (l = priv->children; l != NULL; l = l->next) { info = l->data; if (info->widget == child) return info; } return NULL; } static void hdy_squeezer_progress_updated (HdySqueezer *self) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); gtk_widget_queue_draw (GTK_WIDGET (self)); if (!priv->homogeneous) gtk_widget_queue_resize (GTK_WIDGET (self)); if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER) { if (priv->last_visible_surface != NULL) { cairo_surface_destroy (priv->last_visible_surface); priv->last_visible_surface = NULL; } if (priv->last_visible_child != NULL) { gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE); priv->last_visible_child = NULL; } } } static gboolean hdy_squeezer_transition_cb (GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data) { HdySqueezer *self = HDY_SQUEEZER (widget); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); if (priv->first_frame_skipped) { gtk_progress_tracker_advance_frame (&priv->tracker, gdk_frame_clock_get_frame_time (frame_clock)); } else { priv->first_frame_skipped = TRUE; } /* Finish the animation early if the widget isn't mapped anymore. */ if (!gtk_widget_get_mapped (widget)) gtk_progress_tracker_finish (&priv->tracker); hdy_squeezer_progress_updated (HDY_SQUEEZER (widget)); if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER) { priv->tick_id = 0; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]); return FALSE; } return TRUE; } static void hdy_squeezer_schedule_ticks (HdySqueezer *self) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); if (priv->tick_id == 0) { priv->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), hdy_squeezer_transition_cb, self, NULL); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]); } } static void hdy_squeezer_unschedule_ticks (HdySqueezer *self) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); if (priv->tick_id != 0) { gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->tick_id); priv->tick_id = 0; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]); } } static void hdy_squeezer_start_transition (HdySqueezer *self, HdySqueezerTransitionType transition_type, guint transition_duration) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); GtkWidget *widget = GTK_WIDGET (self); if (gtk_widget_get_mapped (widget) && hdy_get_enable_animations (widget) && transition_type != HDY_SQUEEZER_TRANSITION_TYPE_NONE && transition_duration != 0 && priv->last_visible_child != NULL) { priv->active_transition_type = transition_type; priv->first_frame_skipped = FALSE; hdy_squeezer_schedule_ticks (self); gtk_progress_tracker_start (&priv->tracker, priv->transition_duration * 1000, 0, 1.0); } else { hdy_squeezer_unschedule_ticks (self); priv->active_transition_type = HDY_SQUEEZER_TRANSITION_TYPE_NONE; gtk_progress_tracker_finish (&priv->tracker); } hdy_squeezer_progress_updated (HDY_SQUEEZER (widget)); } static void set_visible_child (HdySqueezer *self, HdySqueezerChildInfo *child_info, HdySqueezerTransitionType transition_type, guint transition_duration) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); HdySqueezerChildInfo *info; GtkWidget *widget = GTK_WIDGET (self); GList *l; GtkWidget *toplevel; GtkWidget *focus; gboolean contains_focus = FALSE; /* If we are being destroyed, do not bother with transitions and * notifications. */ if (gtk_widget_in_destruction (widget)) return; /* If none, pick the first visible. */ if (child_info == NULL) { for (l = priv->children; l != NULL; l = l->next) { info = l->data; if (gtk_widget_get_visible (info->widget)) { child_info = info; break; } } } if (child_info == priv->visible_child) return; toplevel = gtk_widget_get_toplevel (widget); if (GTK_IS_WINDOW (toplevel)) { focus = gtk_window_get_focus (GTK_WINDOW (toplevel)); if (focus && priv->visible_child && priv->visible_child->widget && gtk_widget_is_ancestor (focus, priv->visible_child->widget)) { contains_focus = TRUE; if (priv->visible_child->last_focus) g_object_remove_weak_pointer (G_OBJECT (priv->visible_child->last_focus), (gpointer *)&priv->visible_child->last_focus); priv->visible_child->last_focus = focus; g_object_add_weak_pointer (G_OBJECT (priv->visible_child->last_focus), (gpointer *)&priv->visible_child->last_focus); } } if (priv->last_visible_child != NULL) gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE); priv->last_visible_child = NULL; if (priv->last_visible_surface != NULL) cairo_surface_destroy (priv->last_visible_surface); priv->last_visible_surface = NULL; if (priv->visible_child && priv->visible_child->widget) { if (gtk_widget_is_visible (widget)) { GtkAllocation allocation; priv->last_visible_child = priv->visible_child; gtk_widget_get_allocated_size (priv->last_visible_child->widget, &allocation, NULL); priv->last_visible_widget_width = allocation.width; priv->last_visible_widget_height = allocation.height; } else { gtk_widget_set_child_visible (priv->visible_child->widget, FALSE); } } priv->visible_child = child_info; if (child_info) { gtk_widget_set_child_visible (child_info->widget, TRUE); if (contains_focus) { if (child_info->last_focus) gtk_widget_grab_focus (child_info->last_focus); else gtk_widget_child_focus (child_info->widget, GTK_DIR_TAB_FORWARD); } } if (priv->homogeneous) gtk_widget_queue_allocate (widget); else gtk_widget_queue_resize (widget); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD]); hdy_squeezer_start_transition (self, transition_type, transition_duration); } static void stack_child_visibility_notify_cb (GObject *obj, GParamSpec *pspec, gpointer user_data) { HdySqueezer *self = HDY_SQUEEZER (user_data); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); GtkWidget *child = GTK_WIDGET (obj); HdySqueezerChildInfo *child_info; child_info = find_child_info_for_widget (self, child); if (priv->visible_child == NULL && gtk_widget_get_visible (child)) set_visible_child (self, child_info, priv->transition_type, priv->transition_duration); else if (priv->visible_child == child_info && !gtk_widget_get_visible (child)) set_visible_child (self, NULL, priv->transition_type, priv->transition_duration); if (child_info == priv->last_visible_child) { gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE); priv->last_visible_child = NULL; } } static void hdy_squeezer_add (GtkContainer *container, GtkWidget *child) { HdySqueezer *self = HDY_SQUEEZER (container); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); HdySqueezerChildInfo *child_info; g_return_if_fail (child != NULL); child_info = g_slice_new (HdySqueezerChildInfo); child_info->widget = child; child_info->enabled = TRUE; child_info->last_focus = NULL; priv->children = g_list_append (priv->children, child_info); gtk_widget_set_child_visible (child, FALSE); gtk_widget_set_parent_window (child, priv->bin_window); gtk_widget_set_parent (child, GTK_WIDGET (self)); if (priv->bin_window != NULL) { gdk_window_set_events (priv->bin_window, gdk_window_get_events (priv->bin_window) | gtk_widget_get_events (child)); } g_signal_connect (child, "notify::visible", G_CALLBACK (stack_child_visibility_notify_cb), self); if (priv->visible_child == NULL && gtk_widget_get_visible (child)) set_visible_child (self, child_info, priv->transition_type, priv->transition_duration); if (priv->visible_child == child_info) gtk_widget_queue_resize (GTK_WIDGET (self)); } static void hdy_squeezer_remove (GtkContainer *container, GtkWidget *child) { HdySqueezer *self = HDY_SQUEEZER (container); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); HdySqueezerChildInfo *child_info; gboolean was_visible; child_info = find_child_info_for_widget (self, child); if (child_info == NULL) return; priv->children = g_list_remove (priv->children, child_info); g_signal_handlers_disconnect_by_func (child, stack_child_visibility_notify_cb, self); was_visible = gtk_widget_get_visible (child); child_info->widget = NULL; if (priv->visible_child == child_info) set_visible_child (self, NULL, priv->transition_type, priv->transition_duration); if (priv->last_visible_child == child_info) priv->last_visible_child = NULL; gtk_widget_unparent (child); if (child_info->last_focus) g_object_remove_weak_pointer (G_OBJECT (child_info->last_focus), (gpointer *)&child_info->last_focus); g_slice_free (HdySqueezerChildInfo, child_info); if (priv->homogeneous && was_visible) gtk_widget_queue_resize (GTK_WIDGET (self)); } static void hdy_squeezer_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { HdySqueezer *self = HDY_SQUEEZER (object); switch (property_id) { case PROP_HOMOGENEOUS: g_value_set_boolean (value, hdy_squeezer_get_homogeneous (self)); break; case PROP_VISIBLE_CHILD: g_value_set_object (value, hdy_squeezer_get_visible_child (self)); break; case PROP_TRANSITION_DURATION: g_value_set_uint (value, hdy_squeezer_get_transition_duration (self)); break; case PROP_TRANSITION_TYPE: g_value_set_enum (value, hdy_squeezer_get_transition_type (self)); break; case PROP_TRANSITION_RUNNING: g_value_set_boolean (value, hdy_squeezer_get_transition_running (self)); break; case PROP_INTERPOLATE_SIZE: g_value_set_boolean (value, hdy_squeezer_get_interpolate_size (self)); break; case PROP_ORIENTATION: g_value_set_enum (value, get_orientation (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_squeezer_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { HdySqueezer *self = HDY_SQUEEZER (object); switch (property_id) { case PROP_HOMOGENEOUS: hdy_squeezer_set_homogeneous (self, g_value_get_boolean (value)); break; case PROP_TRANSITION_DURATION: hdy_squeezer_set_transition_duration (self, g_value_get_uint (value)); break; case PROP_TRANSITION_TYPE: hdy_squeezer_set_transition_type (self, g_value_get_enum (value)); break; case PROP_INTERPOLATE_SIZE: hdy_squeezer_set_interpolate_size (self, g_value_get_boolean (value)); break; case PROP_ORIENTATION: set_orientation (self, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void hdy_squeezer_realize (GtkWidget *widget) { HdySqueezer *self = HDY_SQUEEZER (widget); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); GtkAllocation allocation; GdkWindowAttr attributes = { 0 }; GdkWindowAttributesType attributes_mask; HdySqueezerChildInfo *info; GList *l; gtk_widget_set_realized (widget, TRUE); gtk_widget_set_window (widget, g_object_ref (gtk_widget_get_parent_window (widget))); gtk_widget_get_allocation (widget, &allocation); attributes.x = allocation.x; attributes.y = allocation.y; attributes.width = allocation.width; attributes.height = allocation.height; attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.event_mask = gtk_widget_get_events (widget); attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL; priv->view_window = gdk_window_new (gtk_widget_get_window (GTK_WIDGET (self)), &attributes, attributes_mask); gtk_widget_register_window (widget, priv->view_window); attributes.x = 0; attributes.y = 0; attributes.width = allocation.width; attributes.height = allocation.height; for (l = priv->children; l != NULL; l = l->next) { info = l->data; attributes.event_mask |= gtk_widget_get_events (info->widget); } priv->bin_window = gdk_window_new (priv->view_window, &attributes, attributes_mask); gtk_widget_register_window (widget, priv->bin_window); for (l = priv->children; l != NULL; l = l->next) { info = l->data; gtk_widget_set_parent_window (info->widget, priv->bin_window); } gdk_window_show (priv->bin_window); } static void hdy_squeezer_unrealize (GtkWidget *widget) { HdySqueezer *self = HDY_SQUEEZER (widget); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); gtk_widget_unregister_window (widget, priv->bin_window); gdk_window_destroy (priv->bin_window); priv->bin_window = NULL; gtk_widget_unregister_window (widget, priv->view_window); gdk_window_destroy (priv->view_window); priv->view_window = NULL; GTK_WIDGET_CLASS (hdy_squeezer_parent_class)->unrealize (widget); } static void hdy_squeezer_map (GtkWidget *widget) { HdySqueezer *self = HDY_SQUEEZER (widget); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); GTK_WIDGET_CLASS (hdy_squeezer_parent_class)->map (widget); gdk_window_show (priv->view_window); } static void hdy_squeezer_unmap (GtkWidget *widget) { HdySqueezer *self = HDY_SQUEEZER (widget); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); gdk_window_hide (priv->view_window); GTK_WIDGET_CLASS (hdy_squeezer_parent_class)->unmap (widget); } static void hdy_squeezer_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { HdySqueezer *self = HDY_SQUEEZER (container); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); HdySqueezerChildInfo *child_info; GList *l; l = priv->children; while (l) { child_info = l->data; l = l->next; (* callback) (child_info->widget, callback_data); } } static void hdy_squeezer_compute_expand (GtkWidget *widget, gboolean *hexpand_p, gboolean *vexpand_p) { HdySqueezer *self = HDY_SQUEEZER (widget); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); gboolean hexpand, vexpand; HdySqueezerChildInfo *child_info; GtkWidget *child; GList *l; hexpand = FALSE; vexpand = FALSE; for (l = priv->children; l != NULL; l = l->next) { child_info = l->data; child = child_info->widget; if (!hexpand && gtk_widget_compute_expand (child, GTK_ORIENTATION_HORIZONTAL)) hexpand = TRUE; if (!vexpand && gtk_widget_compute_expand (child, GTK_ORIENTATION_VERTICAL)) vexpand = TRUE; if (hexpand && vexpand) break; } *hexpand_p = hexpand; *vexpand_p = vexpand; } static void hdy_squeezer_draw_crossfade (GtkWidget *widget, cairo_t *cr) { HdySqueezer *self = HDY_SQUEEZER (widget); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); gdouble progress = gtk_progress_tracker_get_progress (&priv->tracker, FALSE); cairo_push_group (cr); gtk_container_propagate_draw (GTK_CONTAINER (self), priv->visible_child->widget, cr); cairo_save (cr); /* Multiply alpha by progress. */ cairo_set_source_rgba (cr, 1, 1, 1, progress); cairo_set_operator (cr, CAIRO_OPERATOR_DEST_IN); cairo_paint (cr); if (priv->last_visible_surface != NULL) { cairo_set_source_surface (cr, priv->last_visible_surface, priv->last_visible_surface_allocation.x, priv->last_visible_surface_allocation.y); cairo_set_operator (cr, CAIRO_OPERATOR_ADD); cairo_paint_with_alpha (cr, MAX (1.0 - progress, 0)); } cairo_restore (cr); cairo_pop_group_to_source (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_paint (cr); } static gboolean hdy_squeezer_draw (GtkWidget *widget, cairo_t *cr) { HdySqueezer *self = HDY_SQUEEZER (widget); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); cairo_t *pattern_cr; if (gtk_cairo_should_draw_window (cr, priv->view_window)) { GtkStyleContext *context; context = gtk_widget_get_style_context (widget); gtk_render_background (context, cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget)); } if (priv->visible_child) { if (gtk_progress_tracker_get_state (&priv->tracker) != GTK_PROGRESS_STATE_AFTER) { if (priv->last_visible_surface == NULL && priv->last_visible_child != NULL) { gtk_widget_get_allocation (priv->last_visible_child->widget, &priv->last_visible_surface_allocation); priv->last_visible_surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), CAIRO_CONTENT_COLOR_ALPHA, priv->last_visible_surface_allocation.width, priv->last_visible_surface_allocation.height); pattern_cr = cairo_create (priv->last_visible_surface); /* We don't use propagate_draw here, because we don't want to apply the * bin_window offset. */ gtk_widget_draw (priv->last_visible_child->widget, pattern_cr); cairo_destroy (pattern_cr); } cairo_rectangle (cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget)); cairo_clip (cr); switch (priv->active_transition_type) { case HDY_SQUEEZER_TRANSITION_TYPE_CROSSFADE: if (gtk_cairo_should_draw_window (cr, priv->bin_window)) hdy_squeezer_draw_crossfade (widget, cr); break; case HDY_SQUEEZER_TRANSITION_TYPE_NONE: default: g_assert_not_reached (); } } else if (gtk_cairo_should_draw_window (cr, priv->bin_window)) gtk_container_propagate_draw (GTK_CONTAINER (self), priv->visible_child->widget, cr); } return FALSE; } static void hdy_squeezer_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { HdySqueezer *self = HDY_SQUEEZER (widget); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); HdySqueezerChildInfo *child_info = NULL; GtkWidget *child = NULL; gint child_min; GList *l; GtkAllocation child_allocation; gtk_widget_set_allocation (widget, allocation); for (l = priv->children; l != NULL; l = l->next) { child_info = l->data; child = child_info->widget; if (!gtk_widget_get_visible (child)) continue; if (!child_info->enabled) continue; if (priv->orientation == GTK_ORIENTATION_VERTICAL) { if (gtk_widget_get_request_mode (child) != GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) gtk_widget_get_preferred_height (child, &child_min, NULL); else gtk_widget_get_preferred_height_for_width (child, allocation->width, &child_min, NULL); if (child_min <= allocation->height) break; } else { if (gtk_widget_get_request_mode (child) != GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT) gtk_widget_get_preferred_width (child, &child_min, NULL); else gtk_widget_get_preferred_width_for_height (child, allocation->height, &child_min, NULL); if (child_min <= allocation->width) break; } } set_visible_child (self, child_info, priv->transition_type, priv->transition_duration); child_allocation.x = 0; child_allocation.y = 0; if (gtk_widget_get_realized (widget)) { gdk_window_move_resize (priv->view_window, allocation->x, allocation->y, allocation->width, allocation->height); gdk_window_move_resize (priv->bin_window, 0, 0, allocation->width, allocation->height); } if (priv->last_visible_child != NULL) { int min, nat; gtk_widget_get_preferred_width (priv->last_visible_child->widget, &min, &nat); child_allocation.width = MAX (min, allocation->width); gtk_widget_get_preferred_height_for_width (priv->last_visible_child->widget, child_allocation.width, &min, &nat); child_allocation.height = MAX (min, allocation->height); gtk_widget_size_allocate (priv->last_visible_child->widget, &child_allocation); } child_allocation.width = allocation->width; child_allocation.height = allocation->height; if (priv->visible_child) { int min, nat; GtkAlign valign; gtk_widget_get_preferred_height_for_width (priv->visible_child->widget, allocation->width, &min, &nat); if (priv->interpolate_size) { valign = gtk_widget_get_valign (priv->visible_child->widget); child_allocation.height = MAX (nat, allocation->height); if (valign == GTK_ALIGN_END && child_allocation.height > allocation->height) child_allocation.y -= nat - allocation->height; else if (valign == GTK_ALIGN_CENTER && child_allocation.height > allocation->height) child_allocation.y -= (nat - allocation->height) / 2; } gtk_widget_size_allocate (priv->visible_child->widget, &child_allocation); } } /* This private method is prefixed by the class name because it will be a * virtual method in GTK 4. */ static void hdy_squeezer_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { HdySqueezer *self = HDY_SQUEEZER (widget); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); HdySqueezerChildInfo *child_info; GtkWidget *child; gint child_min, child_nat; GList *l; *minimum = 0; *natural = 0; for (l = priv->children; l != NULL; l = l->next) { child_info = l->data; child = child_info->widget; if (priv->orientation != orientation && !priv->homogeneous && priv->visible_child != child_info) continue; if (!gtk_widget_get_visible (child)) continue; /* Disabled children are taken into account when measuring the widget, to * keep its size request and allocation consistent. This avoids the * appearant size and position of a child to changes suddenly when a larger * child gets enabled/disabled. */ if (orientation == GTK_ORIENTATION_VERTICAL) { if (for_size < 0) gtk_widget_get_preferred_height (child, &child_min, &child_nat); else gtk_widget_get_preferred_height_for_width (child, for_size, &child_min, &child_nat); } else { if (for_size < 0) gtk_widget_get_preferred_width (child, &child_min, &child_nat); else gtk_widget_get_preferred_width_for_height (child, for_size, &child_min, &child_nat); } if (priv->orientation == orientation) *minimum = *minimum == 0 ? child_min : MIN (*minimum, child_min); else *minimum = MAX (*minimum, child_min); *natural = MAX (*natural, child_nat); } if (priv->orientation != orientation && !priv->homogeneous && priv->interpolate_size && priv->last_visible_child != NULL) { gdouble t = gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE); if (orientation == GTK_ORIENTATION_VERTICAL) { *minimum = hdy_lerp (*minimum, priv->last_visible_widget_height, t); *natural = hdy_lerp (*natural, priv->last_visible_widget_height, t); } else { *minimum = hdy_lerp (*minimum, priv->last_visible_widget_width, t); *natural = hdy_lerp (*natural, priv->last_visible_widget_width, t); } } } static void hdy_squeezer_get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural) { hdy_squeezer_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, minimum, natural, NULL, NULL); } static void hdy_squeezer_get_preferred_width_for_height (GtkWidget *widget, gint height, gint *minimum, gint *natural) { hdy_squeezer_measure (widget, GTK_ORIENTATION_HORIZONTAL, height, minimum, natural, NULL, NULL); } static void hdy_squeezer_get_preferred_height (GtkWidget *widget, gint *minimum, gint *natural) { hdy_squeezer_measure (widget, GTK_ORIENTATION_VERTICAL, -1, minimum, natural, NULL, NULL); } static void hdy_squeezer_get_preferred_height_for_width (GtkWidget *widget, gint width, gint *minimum, gint *natural) { hdy_squeezer_measure (widget, GTK_ORIENTATION_VERTICAL, width, minimum, natural, NULL, NULL); } static void hdy_squeezer_get_child_property (GtkContainer *container, GtkWidget *widget, guint property_id, GValue *value, GParamSpec *pspec) { HdySqueezer *self = HDY_SQUEEZER (container); HdySqueezerChildInfo *child_info; child_info = find_child_info_for_widget (self, widget); if (child_info == NULL) { g_param_value_set_default (pspec, value); return; } switch (property_id) { case CHILD_PROP_ENABLED: g_value_set_boolean (value, hdy_squeezer_get_child_enabled (self, widget)); break; default: GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); break; } } static void hdy_squeezer_set_child_property (GtkContainer *container, GtkWidget *widget, guint property_id, const GValue *value, GParamSpec *pspec) { HdySqueezer *self = HDY_SQUEEZER (container); HdySqueezerChildInfo *child_info; child_info = find_child_info_for_widget (self, widget); if (child_info == NULL) return; switch (property_id) { case CHILD_PROP_ENABLED: hdy_squeezer_set_child_enabled (self, widget, g_value_get_boolean (value)); break; default: GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); break; } } static void hdy_squeezer_dispose (GObject *object) { HdySqueezer *self = HDY_SQUEEZER (object); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); priv->visible_child = NULL; G_OBJECT_CLASS (hdy_squeezer_parent_class)->dispose (object); } static void hdy_squeezer_finalize (GObject *object) { HdySqueezer *self = HDY_SQUEEZER (object); HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); hdy_squeezer_unschedule_ticks (self); if (priv->last_visible_surface != NULL) cairo_surface_destroy (priv->last_visible_surface); G_OBJECT_CLASS (hdy_squeezer_parent_class)->finalize (object); } static void hdy_squeezer_class_init (HdySqueezerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->get_property = hdy_squeezer_get_property; object_class->set_property = hdy_squeezer_set_property; object_class->dispose = hdy_squeezer_dispose; object_class->finalize = hdy_squeezer_finalize; widget_class->size_allocate = hdy_squeezer_size_allocate; widget_class->draw = hdy_squeezer_draw; widget_class->realize = hdy_squeezer_realize; widget_class->unrealize = hdy_squeezer_unrealize; widget_class->map = hdy_squeezer_map; widget_class->unmap = hdy_squeezer_unmap; widget_class->get_preferred_height = hdy_squeezer_get_preferred_height; widget_class->get_preferred_height_for_width = hdy_squeezer_get_preferred_height_for_width; widget_class->get_preferred_width = hdy_squeezer_get_preferred_width; widget_class->get_preferred_width_for_height = hdy_squeezer_get_preferred_width_for_height; widget_class->compute_expand = hdy_squeezer_compute_expand; container_class->add = hdy_squeezer_add; container_class->remove = hdy_squeezer_remove; container_class->forall = hdy_squeezer_forall; container_class->set_child_property = hdy_squeezer_set_child_property; container_class->get_child_property = hdy_squeezer_get_child_property; gtk_container_class_handle_border_width (container_class); g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); props[PROP_HOMOGENEOUS] = g_param_spec_boolean ("homogeneous", _("Homogeneous"), _("Homogeneous sizing"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_VISIBLE_CHILD] = g_param_spec_object ("visible-child", _("Visible child"), _("The widget currently visible in the squeezer"), GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_TRANSITION_DURATION] = g_param_spec_uint ("transition-duration", _("Transition duration"), _("The animation duration, in milliseconds"), 0, G_MAXUINT, 200, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_TRANSITION_TYPE] = g_param_spec_enum ("transition-type", _("Transition type"), _("The type of animation used to transition"), HDY_TYPE_SQUEEZER_TRANSITION_TYPE, HDY_SQUEEZER_TRANSITION_TYPE_NONE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_TRANSITION_RUNNING] = g_param_spec_boolean ("transition-running", _("Transition running"), _("Whether or not the transition is currently running"), FALSE, G_PARAM_READABLE); props[PROP_INTERPOLATE_SIZE] = g_param_spec_boolean ("interpolate-size", _("Interpolate size"), _("Whether or not the size should smoothly change when changing between differently sized children"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); child_props[CHILD_PROP_ENABLED] = g_param_spec_boolean ("enabled", _("Enabled"), _("Whether the child can be picked or should be ignored when looking for the child fitting the available size best"), TRUE, G_PARAM_READWRITE); gtk_container_class_install_child_properties (container_class, LAST_CHILD_PROP, child_props); gtk_widget_class_set_css_name (widget_class, "hdysqueezer"); } static void hdy_squeezer_init (HdySqueezer *self) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); priv->homogeneous = TRUE; priv->transition_duration = 200; priv->transition_type = HDY_SQUEEZER_TRANSITION_TYPE_NONE; } /** * hdy_squeezer_new: * * Creates a new #HdySqueezer container. * * Returns: a new #HdySqueezer */ HdySqueezer * hdy_squeezer_new (void) { return g_object_new (HDY_TYPE_SQUEEZER, NULL); } /** * hdy_squeezer_get_homogeneous: * @self: a #HdySqueezer * * Gets whether @self is homogeneous. * * See hdy_squeezer_set_homogeneous(). * * Returns: %TRUE if @self is homogeneous, %FALSE is not * * Since: 0.0.10 */ gboolean hdy_squeezer_get_homogeneous (HdySqueezer *self) { HdySqueezerPrivate *priv; g_return_val_if_fail (HDY_IS_SQUEEZER (self), FALSE); priv = hdy_squeezer_get_instance_private (self); return priv->homogeneous; } /** * hdy_squeezer_set_homogeneous: * @self: a #HdySqueezer * @homogeneous: %TRUE to make @self homogeneous * * Sets @self to be homogeneous or not. If it is homogeneous, @self will request * the same size for all its children for its opposite orientation, e.g. if * @self is oriented horizontally and is homogeneous, it will request the same * height for all its children. If it isn't, @self may change size when a * different child becomes visible. * * Since: 0.0.10 */ void hdy_squeezer_set_homogeneous (HdySqueezer *self, gboolean homogeneous) { HdySqueezerPrivate *priv; g_return_if_fail (HDY_IS_SQUEEZER (self)); priv = hdy_squeezer_get_instance_private (self); homogeneous = !!homogeneous; if (priv->homogeneous == homogeneous) return; priv->homogeneous = homogeneous; if (gtk_widget_get_visible (GTK_WIDGET(self))) gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_HOMOGENEOUS]); } /** * hdy_squeezer_get_transition_duration: * @self: a #HdySqueezer * * Gets the amount of time (in milliseconds) that transitions between children * in @self will take. * * Returns: the transition duration */ guint hdy_squeezer_get_transition_duration (HdySqueezer *self) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); g_return_val_if_fail (HDY_IS_SQUEEZER (self), 0); return priv->transition_duration; } /** * hdy_squeezer_set_transition_duration: * @self: a #HdySqueezer * @duration: the new duration, in milliseconds * * Sets the duration that transitions between children in @self will take. */ void hdy_squeezer_set_transition_duration (HdySqueezer *self, guint duration) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); g_return_if_fail (HDY_IS_SQUEEZER (self)); if (priv->transition_duration == duration) return; priv->transition_duration = duration; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_DURATION]); } /** * hdy_squeezer_get_transition_type: * @self: a #HdySqueezer * * Gets the type of animation that will be used for transitions between children * in @self. * * Returns: the current transition type of @self */ HdySqueezerTransitionType hdy_squeezer_get_transition_type (HdySqueezer *self) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); g_return_val_if_fail (HDY_IS_SQUEEZER (self), HDY_SQUEEZER_TRANSITION_TYPE_NONE); return priv->transition_type; } /** * hdy_squeezer_set_transition_type: * @self: a #HdySqueezer * @transition: the new transition type * * Sets the type of animation that will be used for transitions between children * in @self. Available types include various kinds of fades and slides. * * The transition type can be changed without problems at runtime, so it is * possible to change the animation based on the child that is about to become * current. */ void hdy_squeezer_set_transition_type (HdySqueezer *self, HdySqueezerTransitionType transition) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); g_return_if_fail (HDY_IS_SQUEEZER (self)); if (priv->transition_type == transition) return; priv->transition_type = transition; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_TYPE]); } /** * hdy_squeezer_get_transition_running: * @self: a #HdySqueezer * * Gets whether @self is currently in a transition from one child to another. * * Returns: %TRUE if the transition is currently running, %FALSE otherwise. */ gboolean hdy_squeezer_get_transition_running (HdySqueezer *self) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); g_return_val_if_fail (HDY_IS_SQUEEZER (self), FALSE); return (priv->tick_id != 0); } /** * hdy_squeezer_get_interpolate_size: * @self: A #HdySqueezer * * Gets wether @self should interpolate its size on visible child change. * * See hdy_squeezer_set_interpolate_size(). * * Returns: %TRUE if @self interpolates its size on visible child change, %FALSE if not * * Since: 0.0.10 */ gboolean hdy_squeezer_get_interpolate_size (HdySqueezer *self) { HdySqueezerPrivate *priv; g_return_val_if_fail (HDY_IS_SQUEEZER (self), FALSE); priv = hdy_squeezer_get_instance_private (self); return priv->interpolate_size; } /** * hdy_squeezer_set_interpolate_size: * @self: A #HdySqueezer * @interpolate_size: %TRUE to interpolate the size * * Sets whether or not @self will interpolate the size of its opposing * orientation when changing the visible child. If %TRUE, @self will interpolate * its size between the one of the previous visible child and the one of the new * visible child, according to the set transition duration and the orientation, * e.g. if @self is horizontal, it will interpolate the its height. * * Since: 0.0.10 */ void hdy_squeezer_set_interpolate_size (HdySqueezer *self, gboolean interpolate_size) { HdySqueezerPrivate *priv; g_return_if_fail (HDY_IS_SQUEEZER (self)); priv = hdy_squeezer_get_instance_private (self); interpolate_size = !!interpolate_size; if (priv->interpolate_size == interpolate_size) return; priv->interpolate_size = interpolate_size; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INTERPOLATE_SIZE]); } /** * hdy_squeezer_get_visible_child: * @self: a #HdySqueezer * * Gets the currently visible child of @self, or %NULL if there are no visible * children. * * Returns: (transfer none) (nullable): the visible child of the #HdySqueezer */ GtkWidget * hdy_squeezer_get_visible_child (HdySqueezer *self) { HdySqueezerPrivate *priv = hdy_squeezer_get_instance_private (self); g_return_val_if_fail (HDY_IS_SQUEEZER (self), NULL); return priv->visible_child ? priv->visible_child->widget : NULL; } /** * hdy_squeezer_get_child_enabled: * @self: a #HdySqueezer * @child: a child of @self * * Gets whether @child is enabled. * * See hdy_squeezer_set_child_enabled(). * * Returns: %TRUE if @child is enabled, %FALSE otherwise. */ gboolean hdy_squeezer_get_child_enabled (HdySqueezer *self, GtkWidget *child) { HdySqueezerChildInfo *child_info; g_return_val_if_fail (HDY_IS_SQUEEZER (self), FALSE); g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE); child_info = find_child_info_for_widget (self, child); g_return_val_if_fail (child_info != NULL, FALSE); return child_info->enabled; } /** * hdy_squeezer_set_child_enabled: * @self: a #HdySqueezer * @child: a child of @self * @enabled: %TRUE to enable the child, %FALSE to disable it * * Make @self enable or disable @child. If a child is disabled, it will be * ignored when looking for the child fitting the available size best. This * allows to programmatically and prematurely hide a child of @self even if it * fits in the available space. * * This can be used e.g. to ensure a certain child is hidden below a certain * window width, or any other constraint you find suitable. */ void hdy_squeezer_set_child_enabled (HdySqueezer *self, GtkWidget *child, gboolean enabled) { HdySqueezerChildInfo *child_info; g_return_if_fail (HDY_IS_SQUEEZER (self)); g_return_if_fail (GTK_IS_WIDGET (child)); child_info = find_child_info_for_widget (self, child); g_return_if_fail (child_info != NULL); enabled = !!enabled; if (child_info->enabled == enabled) return; child_info->enabled = enabled; gtk_widget_queue_resize (GTK_WIDGET (self)); } libhandy-0.0.13/src/hdy-squeezer.h000066400000000000000000000037321360136463700167700ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include "hdy-enums.h" G_BEGIN_DECLS #define HDY_TYPE_SQUEEZER (hdy_squeezer_get_type ()) G_DECLARE_DERIVABLE_TYPE (HdySqueezer, hdy_squeezer, HDY, SQUEEZER, GtkContainer) typedef enum { HDY_SQUEEZER_TRANSITION_TYPE_NONE, HDY_SQUEEZER_TRANSITION_TYPE_CROSSFADE, } HdySqueezerTransitionType; /** * HdySqueezerClass * @parent_class: The parent class */ struct _HdySqueezerClass { GtkContainerClass parent_class; }; HdySqueezer *hdy_squeezer_new (void); gboolean hdy_squeezer_get_homogeneous (HdySqueezer *self); void hdy_squeezer_set_homogeneous (HdySqueezer *self, gboolean homogeneous); guint hdy_squeezer_get_transition_duration (HdySqueezer *self); void hdy_squeezer_set_transition_duration (HdySqueezer *self, guint duration); HdySqueezerTransitionType hdy_squeezer_get_transition_type (HdySqueezer *self); void hdy_squeezer_set_transition_type (HdySqueezer *self, HdySqueezerTransitionType transition); gboolean hdy_squeezer_get_transition_running (HdySqueezer *self); gboolean hdy_squeezer_get_interpolate_size (HdySqueezer *self); void hdy_squeezer_set_interpolate_size (HdySqueezer *self, gboolean interpolate_size); GtkWidget *hdy_squeezer_get_visible_child (HdySqueezer *self); gboolean hdy_squeezer_get_child_enabled (HdySqueezer *self, GtkWidget *child); void hdy_squeezer_set_child_enabled (HdySqueezer *self, GtkWidget *child, gboolean enabled); G_END_DECLS libhandy-0.0.13/src/hdy-string-utf8.c000066400000000000000000000022671360136463700173140ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "hdy-string-utf8.h" /** * SECTION:hdy-string-utf8 * @short_description: #GString utf-8 helpers. * @title: HdyStringUtf8 * * Helpers to ease utf-8 handling based on #GString. */ /** * hdy_string_utf8_truncate: * @string: a #GString * @len: the new size of the string * * Cut of the end of the string @string so that @len utf8 characters remain. * * Returns: (transfer none): @string */ GString * hdy_string_utf8_truncate (GString *string, gsize len) { gint cutoff; gchar *off; g_return_val_if_fail (string != NULL, NULL); cutoff = MIN (len, g_utf8_strlen (string->str, -1)); off = g_utf8_offset_to_pointer (string->str, cutoff); g_string_truncate (string, off - string->str); return string; } /** * hdy_string_utf8_len: * @string: a #GString * * Computes the length of the string in utf-8 characters. See #g_utf8_strlen. * * Returns: the length of @string in characters */ glong hdy_string_utf8_len (GString *string) { g_return_val_if_fail (string != NULL, 0); return g_utf8_strlen (string->str, -1); } libhandy-0.0.13/src/hdy-string-utf8.h000066400000000000000000000006661360136463700173220ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif G_BEGIN_DECLS GString* hdy_string_utf8_truncate (GString *string, gsize len); glong hdy_string_utf8_len (GString *string); G_END_DECLS libhandy-0.0.13/src/hdy-style-private.h000066400000000000000000000010541360136463700177300ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS /* The style provider priority to use for libhandy widgets custom styling. It is * higher than settings but lower than applications, so application developers * can nonetheless apply custom styling on top of it. */ #define HDY_STYLE_PROVIDER_PRIORITY (GTK_STYLE_PROVIDER_PRIORITY_SETTINGS + 1) G_END_DECLS libhandy-0.0.13/src/hdy-swipe-group.c000066400000000000000000000351141360136463700174000ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "hdy-swipe-group.h" #include #include "hdy-swipeable-private.h" #define BUILDABLE_TAG_OBJECT "object" #define BUILDABLE_TAG_SWIPEABLE "swipeable" #define BUILDABLE_TAG_SWIPEABLES "swipeables" #define BUILDABLE_TAG_TEMPLATE "template" /** * SECTION:hdy-swipe-group * @short_description: An object for syncing swipeable widgets. * @title: HdySwipeGroup * @See_also: #HdyLeaflet, #HdyPaginator, #HdySwipeable * * The #HdySwipeGroup object can be used to sync multiple swipeable widgets * that implement the #HdySwipeable interface, such as #HdyPaginator, so that * animating one of them also animates all the other widgets in the group. * * This can be useful for syncing widgets between a window's titlebar and * content area. * * # #HdySwipeGroup as #GtkBuildable * * #HdySwipeGroup can be created in an UI definition. The list of swipeable * widgets is specified with a <swipeables> element containing multiple * <swipeable> elements with their ”name” attribute specifying the id of * the widgets. * * |[ * * * * * * * ]| * * Since: 0.0.12 */ struct _HdySwipeGroup { GObject parent_instance; GSList *swipeables; HdySwipeable *current; }; static void hdy_swipe_group_buildable_init (GtkBuildableIface *iface); G_DEFINE_TYPE_WITH_CODE (HdySwipeGroup, hdy_swipe_group, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, hdy_swipe_group_buildable_init)) static gboolean contains (HdySwipeGroup *self, HdySwipeable *swipeable) { GSList *swipeables; for (swipeables = self->swipeables; swipeables != NULL; swipeables = swipeables->next) if (swipeables->data == swipeable) return TRUE; return FALSE; } static void swipeable_destroyed (HdySwipeGroup *self, HdySwipeable *swipeable) { g_return_if_fail (HDY_IS_SWIPE_GROUP (self)); self->swipeables = g_slist_remove (self->swipeables, swipeable); g_object_unref (self); } /** * hdy_swipe_group_new: * * Create a new #HdySwipeGroup object. * * Returns: The newly created #HdySwipeGroup object * * Since: 0.0.12 */ HdySwipeGroup * hdy_swipe_group_new (void) { return g_object_new (HDY_TYPE_SWIPE_GROUP, NULL); } static void switch_child_cb (HdySwipeGroup *self, uint index, gint64 duration, HdySwipeable *swipeable) { GSList *swipeables; if (self->current != NULL && self->current != swipeable) return; for (swipeables = self->swipeables; swipeables != NULL; swipeables = swipeables->next) if (swipeables->data != swipeable) hdy_swipeable_switch_child (swipeables->data, index, duration); } static void begin_swipe_cb (HdySwipeGroup *self, gint direction, HdySwipeable *swipeable) { GSList *swipeables; if (self->current != NULL && self->current != swipeable) return; self->current = swipeable; for (swipeables = self->swipeables; swipeables != NULL; swipeables = swipeables->next) if (swipeables->data != swipeable) hdy_swipeable_begin_swipe (swipeables->data, direction, FALSE); } static void update_swipe_cb (HdySwipeGroup *self, gdouble value, HdySwipeable *swipeable) { GSList *swipeables; if (swipeable != self->current) return; for (swipeables = self->swipeables; swipeables != NULL; swipeables = swipeables->next) if (swipeables->data != swipeable) hdy_swipeable_update_swipe (swipeables->data, value); } static void end_swipe_cb (HdySwipeGroup *self, gint64 duration, gdouble to, HdySwipeable *swipeable) { GSList *swipeables; if (swipeable != self->current) return; for (swipeables = self->swipeables; swipeables != NULL; swipeables = swipeables->next) if (swipeables->data != swipeable) hdy_swipeable_end_swipe (swipeables->data, duration, to); self->current = NULL; } /** * hdy_swipe_group_add_swipeable: * @self: a #HdySwipeGroup * @swipeable: the #HdySwipeable to add * * When the widget is destroyed or no longer referenced elsewhere, it will * be removed from the swipe group. * * Since: 0.0.12 */ void hdy_swipe_group_add_swipeable (HdySwipeGroup *self, HdySwipeable *swipeable) { g_return_if_fail (HDY_IS_SWIPE_GROUP (self)); g_return_if_fail (HDY_IS_SWIPEABLE (swipeable)); g_signal_connect_swapped (swipeable, "switch-child", G_CALLBACK (switch_child_cb), self); g_signal_connect_swapped (swipeable, "begin-swipe", G_CALLBACK (begin_swipe_cb), self); g_signal_connect_swapped (swipeable, "update-swipe", G_CALLBACK (update_swipe_cb), self); g_signal_connect_swapped (swipeable, "end-swipe", G_CALLBACK (end_swipe_cb), self); self->swipeables = g_slist_prepend (self->swipeables, swipeable); g_object_ref (self); g_signal_connect_swapped (swipeable, "destroy", G_CALLBACK (swipeable_destroyed), self); } /** * hdy_swipe_group_remove_swipeable: * @self: a #HdySwipeGroup * @swipeable: the #HdySwipeable to remove * * Removes a widget from a #HdySwipeGroup. * * Since: 0.0.12 **/ void hdy_swipe_group_remove_swipeable (HdySwipeGroup *self, HdySwipeable *swipeable) { g_return_if_fail (HDY_IS_SWIPE_GROUP (self)); g_return_if_fail (HDY_IS_SWIPEABLE (swipeable)); g_return_if_fail (contains (self, swipeable)); self->swipeables = g_slist_remove (self->swipeables, swipeable); g_signal_handlers_disconnect_by_data (swipeable, self); g_object_unref (self); } /** * hdy_swipe_group_get_swipeables: * @self: a #HdySwipeGroup * * Returns the list of swipeables associated with @self. * * Returns: (element-type HdySwipeable) (transfer none): a #GSList of * swipeables. The list is owned by libhandy and should not be modified. * * Since: 0.0.12 **/ GSList * hdy_swipe_group_get_swipeables (HdySwipeGroup *self) { g_return_val_if_fail (HDY_IS_SWIPE_GROUP (self), NULL); return self->swipeables; } typedef struct { gchar *name; gint line; gint col; } ItemData; static void item_data_free (gpointer data) { ItemData *item_data = data; g_free (item_data->name); g_free (item_data); } typedef struct { GObject *object; GtkBuilder *builder; GSList *items; } GSListSubParserData; static void hdy_swipe_group_dispose (GObject *object) { HdySwipeGroup *self = (HdySwipeGroup *)object; g_slist_free_full (self->swipeables, (GDestroyNotify) g_object_unref); self->swipeables = NULL; G_OBJECT_CLASS (hdy_swipe_group_parent_class)->dispose (object); } /*< private > * @builder: a #GtkBuilder * @context: the #GMarkupParseContext * @parent_name: the name of the expected parent element * @error: return location for an error * * Checks that the parent element of the currently handled * start tag is @parent_name and set @error if it isn't. * * This is intended to be called in start_element vfuncs to * ensure that element nesting is as intended. * * Returns: %TRUE if @parent_name is the parent element */ /* This has been copied and modified from gtkbuilder.c. */ static gboolean _gtk_builder_check_parent (GtkBuilder *builder, GMarkupParseContext *context, const gchar *parent_name, GError **error) { const GSList *stack; gint line, col; const gchar *parent; const gchar *element; stack = g_markup_parse_context_get_element_stack (context); element = (const gchar *)stack->data; parent = stack->next ? (const gchar *)stack->next->data : ""; if (g_str_equal (parent_name, parent) || (g_str_equal (parent_name, BUILDABLE_TAG_OBJECT) && g_str_equal (parent, BUILDABLE_TAG_TEMPLATE))) return TRUE; g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, GTK_BUILDER_ERROR, GTK_BUILDER_ERROR_INVALID_TAG, ".:%d:%d Can't use <%s> here", line, col, element); return FALSE; } /*< private > * _gtk_builder_prefix_error: * @builder: a #GtkBuilder * @context: the #GMarkupParseContext * @error: an error * * Calls g_prefix_error() to prepend a filename:line:column marker * to the given error. The filename is taken from @builder, and * the line and column are obtained by calling * g_markup_parse_context_get_position(). * * This is intended to be called on errors returned by * g_markup_collect_attributes() in a start_element vfunc. */ /* This has been copied and modified from gtkbuilder.c. */ static void _gtk_builder_prefix_error (GtkBuilder *builder, GMarkupParseContext *context, GError **error) { gint line, col; g_markup_parse_context_get_position (context, &line, &col); g_prefix_error (error, ".:%d:%d ", line, col); } /*< private > * _gtk_builder_error_unhandled_tag: * @builder: a #GtkBuilder * @context: the #GMarkupParseContext * @object: name of the object that is being handled * @element_name: name of the element whose start tag is being handled * @error: return location for the error * * Sets @error to a suitable error indicating that an @element_name * tag is not expected in the custom markup for @object. * * This is intended to be called in a start_element vfunc. */ /* This has been copied and modified from gtkbuilder.c. */ static void _gtk_builder_error_unhandled_tag (GtkBuilder *builder, GMarkupParseContext *context, const gchar *object, const gchar *element_name, GError **error) { gint line, col; g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, GTK_BUILDER_ERROR, GTK_BUILDER_ERROR_UNHANDLED_TAG, ".:%d:%d Unsupported tag for %s: <%s>", line, col, object, element_name); } /* This has been copied and modified from gtksizegroup.c. */ static void swipe_group_start_element (GMarkupParseContext *context, const gchar *element_name, const gchar **names, const gchar **values, gpointer user_data, GError **error) { GSListSubParserData *data = (GSListSubParserData*)user_data; if (strcmp (element_name, BUILDABLE_TAG_SWIPEABLE) == 0) { const gchar *name; ItemData *item_data; if (!_gtk_builder_check_parent (data->builder, context, BUILDABLE_TAG_SWIPEABLES, error)) return; if (!g_markup_collect_attributes (element_name, names, values, error, G_MARKUP_COLLECT_STRING, "name", &name, G_MARKUP_COLLECT_INVALID)) { _gtk_builder_prefix_error (data->builder, context, error); return; } item_data = g_new (ItemData, 1); item_data->name = g_strdup (name); g_markup_parse_context_get_position (context, &item_data->line, &item_data->col); data->items = g_slist_prepend (data->items, item_data); } else if (strcmp (element_name, BUILDABLE_TAG_SWIPEABLES) == 0) { if (!_gtk_builder_check_parent (data->builder, context, BUILDABLE_TAG_OBJECT, error)) return; if (!g_markup_collect_attributes (element_name, names, values, error, G_MARKUP_COLLECT_INVALID, NULL, NULL, G_MARKUP_COLLECT_INVALID)) _gtk_builder_prefix_error (data->builder, context, error); } else { _gtk_builder_error_unhandled_tag (data->builder, context, "HdySwipeGroup", element_name, error); } } /* This has been copied and modified from gtksizegroup.c. */ static const GMarkupParser swipe_group_parser = { swipe_group_start_element }; /* This has been copied and modified from gtksizegroup.c. */ static gboolean hdy_swipe_group_buildable_custom_tag_start (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, GMarkupParser *parser, gpointer *parser_data) { GSListSubParserData *data; if (child) return FALSE; if (strcmp (tagname, BUILDABLE_TAG_SWIPEABLES) == 0) { data = g_slice_new0 (GSListSubParserData); data->items = NULL; data->object = G_OBJECT (buildable); data->builder = builder; *parser = swipe_group_parser; *parser_data = data; return TRUE; } return FALSE; } /* This has been copied and modified from gtksizegroup.c. */ static void hdy_swipe_group_buildable_custom_finished (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, gpointer user_data) { GSList *l; GSListSubParserData *data; GObject *object; if (strcmp (tagname, BUILDABLE_TAG_SWIPEABLES) != 0) return; data = (GSListSubParserData*)user_data; data->items = g_slist_reverse (data->items); for (l = data->items; l; l = l->next) { ItemData *item_data = l->data; object = gtk_builder_get_object (builder, item_data->name); if (!object) continue; hdy_swipe_group_add_swipeable (HDY_SWIPE_GROUP (data->object), HDY_SWIPEABLE (object)); } g_slist_free_full (data->items, item_data_free); g_slice_free (GSListSubParserData, data); } static void hdy_swipe_group_class_init (HdySwipeGroupClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = hdy_swipe_group_dispose; } static void hdy_swipe_group_init (HdySwipeGroup *self) { } static void hdy_swipe_group_buildable_init (GtkBuildableIface *iface) { iface->custom_tag_start = hdy_swipe_group_buildable_custom_tag_start; iface->custom_finished = hdy_swipe_group_buildable_custom_finished; } libhandy-0.0.13/src/hdy-swipe-group.h000066400000000000000000000015531360136463700174050ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include "hdy-swipeable.h" G_BEGIN_DECLS #define HDY_TYPE_SWIPE_GROUP (hdy_swipe_group_get_type()) G_DECLARE_FINAL_TYPE (HdySwipeGroup, hdy_swipe_group, HDY, SWIPE_GROUP, GObject) HdySwipeGroup *hdy_swipe_group_new (void); void hdy_swipe_group_add_swipeable (HdySwipeGroup *self, HdySwipeable *swipeable); GSList * hdy_swipe_group_get_swipeables (HdySwipeGroup *self); void hdy_swipe_group_remove_swipeable (HdySwipeGroup *self, HdySwipeable *swipeable); G_END_DECLS libhandy-0.0.13/src/hdy-swipe-tracker-private.h000066400000000000000000000035271360136463700213570ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include "hdy-swipeable-private.h" G_BEGIN_DECLS #define HDY_TYPE_SWIPE_TRACKER (hdy_swipe_tracker_get_type()) G_DECLARE_FINAL_TYPE (HdySwipeTracker, hdy_swipe_tracker, HDY, SWIPE_TRACKER, GObject) HdySwipeTracker *hdy_swipe_tracker_new (HdySwipeable *swipeable); gboolean hdy_swipe_tracker_get_enabled (HdySwipeTracker *self); void hdy_swipe_tracker_set_enabled (HdySwipeTracker *self, gboolean enabled); gboolean hdy_swipe_tracker_get_reversed (HdySwipeTracker *self); void hdy_swipe_tracker_set_reversed (HdySwipeTracker *self, gboolean reversed); gboolean hdy_swipe_tracker_get_allow_mouse_drag (HdySwipeTracker *self); void hdy_swipe_tracker_set_allow_mouse_drag (HdySwipeTracker *self, gboolean allow_mouse_drag); gboolean hdy_swipe_tracker_captured_event (HdySwipeTracker *self, GdkEvent *event); void hdy_swipe_tracker_confirm_swipe (HdySwipeTracker *self, gdouble distance, gdouble *snap_points, gint n_snap_points, gdouble current_progress, gdouble cancel_progress); G_END_DECLS libhandy-0.0.13/src/hdy-swipe-tracker.c000066400000000000000000000637771360136463700177170ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-swipe-tracker-private.h" #include #define TOUCHPAD_BASE_DISTANCE_H 400 #define TOUCHPAD_BASE_DISTANCE_V 300 #define SCROLL_MULTIPLIER 10 #define MIN_ANIMATION_DURATION 100 #define MAX_ANIMATION_DURATION 400 #define VELOCITY_THRESHOLD 0.4 #define DURATION_MULTIPLIER 3 #define ANIMATION_BASE_VELOCITY 0.002 #define DRAG_THRESHOLD_DISTANCE 5 /** * PRIVATE:hdy-swipe-tracker * @short_description: Swipe tracker used in #HdyLeaflet and #HdyPaginator * @title: HdySwipeTracker * @See_also: #HdyLeaflet, #HdyPaginator, #HdySwipeable * @stability: Private * * The HdySwipeTracker object can be used for implementing widgets with swipe * gestures. It supports touch-based swipes, pointer dragging, and touchpad * scrolling. * * The events must be received as early as possible to defer the events to * child widgets when needed. Usually this happens naturally, but * GtkScrolledWindow receives events on capture phase via a private function. * Because of that, implementing widgets must do the same thing, i.e. receive * events on capture phase and call hdy_swipe_tracker_captured_event() for * each event. This can be done as follows: * |[ * g_object_set_data (G_OBJECT (self), "captured-event-handler", captured_event_cb); * ]| * Where captured_event_cb() is: * |[ * static gboolean * captured_event_cb (MyWidget *self, GdkEvent *event) * { * return hdy_swipe_tracker_captured_event (self->tracker, event); * } * ]| * * NOTE: In GTK4 this can be replaced by a GtkEventControllerLegacy with capture * propagation phase. * * The widgets will probably want to expose #HdySwipeTracker:enabled property. * If they expect to use horizontal orientation, #HdySwipeTracker:reversed * property can be used for supporting RTL text direction. * * Since: 0.0.11 */ typedef enum { HDY_SWIPE_TRACKER_STATE_NONE, HDY_SWIPE_TRACKER_STATE_PREPARING, HDY_SWIPE_TRACKER_STATE_PENDING, HDY_SWIPE_TRACKER_STATE_SCROLLING, HDY_SWIPE_TRACKER_STATE_FINISHING, } HdySwipeTrackerState; struct _HdySwipeTracker { GObject parent_instance; HdySwipeable *swipeable; gboolean enabled; gboolean reversed; gboolean allow_mouse_drag; GtkOrientation orientation; guint32 prev_time; gdouble velocity; gdouble initial_progress; gdouble progress; gboolean cancelled; gdouble cancel_progress; gdouble prev_offset; gdouble distance; gdouble *snap_points; gint n_snap_points; gboolean is_scrolling; HdySwipeTrackerState state; GtkGesture *touch_gesture; }; G_DEFINE_TYPE_WITH_CODE (HdySwipeTracker, hdy_swipe_tracker, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)); enum { PROP_0, PROP_SWIPEABLE, PROP_ENABLED, PROP_REVERSED, PROP_ALLOW_MOUSE_DRAG, /* GtkOrientable */ PROP_ORIENTATION, LAST_PROP = PROP_ALLOW_MOUSE_DRAG + 1, }; static GParamSpec *props[LAST_PROP]; static void reset (HdySwipeTracker *self) { if (self->snap_points) { g_free (self->snap_points); self->snap_points = NULL; } self->state = HDY_SWIPE_TRACKER_STATE_NONE; self->prev_offset = 0; self->distance = 0; self->initial_progress = 0; self->progress = 0; self->prev_time = 0; self->velocity = 0; self->cancel_progress = 0; self->cancelled = FALSE; if (self->swipeable) gtk_grab_remove (GTK_WIDGET (self->swipeable)); } static void gesture_prepare (HdySwipeTracker *self, gint direction) { if (self->state != HDY_SWIPE_TRACKER_STATE_NONE) return; self->state = HDY_SWIPE_TRACKER_STATE_PREPARING; hdy_swipeable_begin_swipe (self->swipeable, direction, TRUE); } static void gesture_begin (HdySwipeTracker *self) { GdkEvent *event; if (self->state != HDY_SWIPE_TRACKER_STATE_PENDING) return; event = gtk_get_current_event (); self->prev_time = gdk_event_get_time (event); self->state = HDY_SWIPE_TRACKER_STATE_SCROLLING; gtk_grab_add (GTK_WIDGET (self->swipeable)); } static void gesture_update (HdySwipeTracker *self, gdouble delta) { GdkEvent *event; guint32 time; gdouble progress; gdouble first_point, last_point; if (self->state != HDY_SWIPE_TRACKER_STATE_SCROLLING) return; event = gtk_get_current_event (); time = gdk_event_get_time (event); if (time != self->prev_time) self->velocity = delta / (time - self->prev_time); first_point = self->snap_points[0]; last_point = self->snap_points[self->n_snap_points - 1]; progress = self->progress + delta; progress = CLAMP (progress, first_point, last_point); /* FIXME: this is a hack to prevent swiping more than 1 page at once */ progress = CLAMP (progress, self->initial_progress - 1, self->initial_progress + 1); self->progress = progress; hdy_swipeable_update_swipe (self->swipeable, progress); self->prev_time = time; } static void get_closest_snap_points (HdySwipeTracker *self, gdouble *upper, gdouble *lower) { gint i; *upper = 0; *lower = 0; for (i = 0; i < self->n_snap_points; i++) { if (self->snap_points[i] >= self->progress) { *upper = self->snap_points[i]; break; } } for (i = self->n_snap_points - 1; i >= 0; i--) { if (self->snap_points[i] <= self->progress) { *lower = self->snap_points[i]; break; } } } static gdouble get_end_progress (HdySwipeTracker *self) { gdouble upper, lower, middle; if (self->cancelled) return self->cancel_progress; get_closest_snap_points (self, &upper, &lower); middle = (upper + lower) / 2; if (self->progress > middle) return (self->velocity * self->distance > -VELOCITY_THRESHOLD || self->initial_progress > upper) ? upper : lower; return (self->velocity * self->distance < VELOCITY_THRESHOLD || self->initial_progress < lower) ? lower : upper; } static void gesture_end (HdySwipeTracker *self) { gdouble end_progress, velocity; gint64 duration; if (self->state == HDY_SWIPE_TRACKER_STATE_NONE) return; end_progress = get_end_progress (self); velocity = ANIMATION_BASE_VELOCITY; if ((end_progress - self->progress) * self->velocity > 0) velocity = self->velocity; duration = ABS ((self->progress - end_progress) / velocity * DURATION_MULTIPLIER); if (self->progress != end_progress) duration = CLAMP (duration, MIN_ANIMATION_DURATION, MAX_ANIMATION_DURATION); hdy_swipeable_end_swipe (self->swipeable, duration, end_progress); if (self->cancelled) reset (self); else self->state = HDY_SWIPE_TRACKER_STATE_FINISHING; } static void gesture_cancel (HdySwipeTracker *self) { if (self->state == HDY_SWIPE_TRACKER_STATE_PREPARING) { reset (self); return; } if (self->state != HDY_SWIPE_TRACKER_STATE_PENDING && self->state != HDY_SWIPE_TRACKER_STATE_SCROLLING) return; self->cancelled = TRUE; gesture_end (self); } static void drag_begin_cb (HdySwipeTracker *self, gdouble start_x, gdouble start_y, GtkGestureDrag *gesture) { if (self->state != HDY_SWIPE_TRACKER_STATE_NONE) gtk_gesture_set_state (self->touch_gesture, GTK_EVENT_SEQUENCE_DENIED); } static void drag_update_cb (HdySwipeTracker *self, gdouble offset_x, gdouble offset_y, GtkGestureDrag *gesture) { gdouble offset; gboolean is_vertical, is_offset_vertical; is_vertical = (self->orientation == GTK_ORIENTATION_VERTICAL); if (is_vertical) offset = -offset_y / self->distance; else offset = -offset_x / self->distance; if (self->reversed) offset = -offset; is_offset_vertical = (ABS (offset_y) > ABS (offset_x)); if (self->state == HDY_SWIPE_TRACKER_STATE_NONE) { if (is_vertical == is_offset_vertical) gesture_prepare (self, offset > 0 ? 1 : -1); else gtk_gesture_set_state (self->touch_gesture, GTK_EVENT_SEQUENCE_DENIED); return; } if (self->state == HDY_SWIPE_TRACKER_STATE_PENDING) { gdouble distance; gdouble first_point, last_point; gboolean is_overshooting; first_point = self->snap_points[0]; last_point = self->snap_points[self->n_snap_points - 1]; distance = sqrt (offset_x * offset_x + offset_y * offset_y); is_overshooting = (offset < 0 && self->progress <= first_point) || (offset > 0 && self->progress >= last_point); if (distance >= DRAG_THRESHOLD_DISTANCE) { if ((is_vertical == is_offset_vertical) && !is_overshooting) { gesture_begin (self); gtk_gesture_set_state (self->touch_gesture, GTK_EVENT_SEQUENCE_CLAIMED); } else { gtk_gesture_set_state (self->touch_gesture, GTK_EVENT_SEQUENCE_DENIED); } } } if (self->state == HDY_SWIPE_TRACKER_STATE_SCROLLING) { gesture_update (self, offset - self->prev_offset); self->prev_offset = offset; } } static void drag_end_cb (HdySwipeTracker *self, gdouble offset_x, gdouble offset_y, GtkGestureDrag *gesture) { if (self->state != HDY_SWIPE_TRACKER_STATE_SCROLLING) { gesture_cancel (self); gtk_gesture_set_state (self->touch_gesture, GTK_EVENT_SEQUENCE_DENIED); return; } gesture_end (self); } static void drag_cancel_cb (HdySwipeTracker *self, GdkEventSequence *sequence, GtkGesture *gesture) { gesture_cancel (self); gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED); } static gboolean captured_scroll_event (HdySwipeTracker *self, GdkEvent *event) { GdkDevice *source_device; GdkInputSource input_source; gdouble dx, dy, delta; gboolean is_vertical; gboolean is_delta_vertical; if (gdk_event_get_scroll_direction (event, NULL)) return GDK_EVENT_PROPAGATE; source_device = gdk_event_get_source_device (event); input_source = gdk_device_get_source (source_device); if (input_source != GDK_SOURCE_TOUCHPAD) return GDK_EVENT_PROPAGATE; is_vertical = (self->orientation == GTK_ORIENTATION_VERTICAL); gdk_event_get_scroll_deltas (event, &dx, &dy); delta = is_vertical ? dy : dx; if (self->reversed) delta = -delta; is_delta_vertical = (ABS (dy) > ABS (dx)); if (self->is_scrolling) { gesture_cancel (self); if (gdk_event_is_scroll_stop_event (event)) self->is_scrolling = FALSE; return GDK_EVENT_PROPAGATE; } if (self->state == HDY_SWIPE_TRACKER_STATE_NONE) { if (gdk_event_is_scroll_stop_event (event)) return GDK_EVENT_PROPAGATE; if (is_vertical == is_delta_vertical) gesture_prepare (self, delta > 0 ? 1 : -1); else { self->is_scrolling = TRUE; return GDK_EVENT_PROPAGATE; } } if (self->state == HDY_SWIPE_TRACKER_STATE_PREPARING) { if (gdk_event_is_scroll_stop_event (event)) gesture_cancel (self); return GDK_EVENT_PROPAGATE; } if (self->state == HDY_SWIPE_TRACKER_STATE_PENDING) { gboolean is_overshooting; gdouble first_point, last_point; first_point = self->snap_points[0]; last_point = self->snap_points[self->n_snap_points - 1]; is_overshooting = (delta < 0 && self->progress <= first_point) || (delta > 0 && self->progress >= last_point); if ((is_vertical == is_delta_vertical) && !is_overshooting) gesture_begin (self); else gesture_cancel (self); } if (self->state == HDY_SWIPE_TRACKER_STATE_SCROLLING) { if (gdk_event_is_scroll_stop_event (event)) { gesture_end (self); } else { self->distance = is_vertical ? TOUCHPAD_BASE_DISTANCE_V : TOUCHPAD_BASE_DISTANCE_H; gesture_update (self, delta / self->distance * SCROLL_MULTIPLIER); return GDK_EVENT_STOP; } } if (self->state == HDY_SWIPE_TRACKER_STATE_FINISHING) reset (self); return GDK_EVENT_PROPAGATE; } static void hdy_swipe_tracker_constructed (GObject *object) { HdySwipeTracker *self = HDY_SWIPE_TRACKER (object); g_assert (self->swipeable); gtk_widget_add_events (GTK_WIDGET (self->swipeable), GDK_SMOOTH_SCROLL_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK | GDK_TOUCH_MASK); self->touch_gesture = g_object_new (GTK_TYPE_GESTURE_DRAG, "widget", self->swipeable, "propagation-phase", GTK_PHASE_NONE, "touch-only", !self->allow_mouse_drag, NULL); g_signal_connect_swapped (self->touch_gesture, "drag-begin", G_CALLBACK (drag_begin_cb), self); g_signal_connect_swapped (self->touch_gesture, "drag-update", G_CALLBACK (drag_update_cb), self); g_signal_connect_swapped (self->touch_gesture, "drag-end", G_CALLBACK (drag_end_cb), self); g_signal_connect_swapped (self->touch_gesture, "cancel", G_CALLBACK (drag_cancel_cb), self); G_OBJECT_CLASS (hdy_swipe_tracker_parent_class)->constructed (object); } static void hdy_swipe_tracker_dispose (GObject *object) { HdySwipeTracker *self = HDY_SWIPE_TRACKER (object); if (self->swipeable) gtk_grab_remove (GTK_WIDGET (self->swipeable)); if (self->touch_gesture) g_signal_handlers_disconnect_by_data (self->touch_gesture, self); g_clear_pointer (&self->snap_points, g_free); g_clear_object (&self->touch_gesture); g_clear_object (&self->swipeable); G_OBJECT_CLASS (hdy_swipe_tracker_parent_class)->dispose (object); } static void hdy_swipe_tracker_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdySwipeTracker *self = HDY_SWIPE_TRACKER (object); switch (prop_id) { case PROP_SWIPEABLE: g_value_set_object (value, self->swipeable); break; case PROP_ENABLED: g_value_set_boolean (value, hdy_swipe_tracker_get_enabled (self)); break; case PROP_REVERSED: g_value_set_boolean (value, hdy_swipe_tracker_get_reversed (self)); break; case PROP_ALLOW_MOUSE_DRAG: g_value_set_boolean (value, hdy_swipe_tracker_get_allow_mouse_drag (self)); break; case PROP_ORIENTATION: g_value_set_enum (value, self->orientation); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_swipe_tracker_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdySwipeTracker *self = HDY_SWIPE_TRACKER (object); switch (prop_id) { case PROP_SWIPEABLE: self->swipeable = HDY_SWIPEABLE (g_object_ref (g_value_get_object (value))); break; case PROP_ENABLED: hdy_swipe_tracker_set_enabled (self, g_value_get_boolean (value)); break; case PROP_REVERSED: hdy_swipe_tracker_set_reversed (self, g_value_get_boolean (value)); break; case PROP_ALLOW_MOUSE_DRAG: hdy_swipe_tracker_set_allow_mouse_drag (self, g_value_get_boolean (value)); break; case PROP_ORIENTATION: { GtkOrientation orientation = g_value_get_enum (value); if (orientation != self->orientation) { self->orientation = g_value_get_enum (value); g_object_notify (G_OBJECT (self), "orientation"); } } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_swipe_tracker_class_init (HdySwipeTrackerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = hdy_swipe_tracker_constructed; object_class->dispose = hdy_swipe_tracker_dispose; object_class->get_property = hdy_swipe_tracker_get_property; object_class->set_property = hdy_swipe_tracker_set_property; /** * HdySwipeTracker:widget: * * The widget the swipe tracker is attached to. Must not be %NULL. * * Since: 0.0.11 */ props[PROP_SWIPEABLE] = g_param_spec_object ("swipeable", _("Swipeable"), _("The swipeable the swipe tracker is attached to"), GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * HdySwipeTracker:enabled: * * Whether the swipe tracker is enabled. When it's not enabled, no events * will be processed. Usually widgets will want to expose this via a property. * * Since: 0.0.11 */ props[PROP_ENABLED] = g_param_spec_boolean ("enabled", _("Enabled"), _("Whether the swipe tracker processes events"), TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdySwipeTracker:reversed: * * Whether to reverse the swipe direction. If the swipe tracker is horizontal, * it can be used for supporting RTL text direction. * * Since: 0.0.11 */ props[PROP_REVERSED] = g_param_spec_boolean ("reversed", _("Reversed"), _("Whether swipe direction is reversed"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdySwipeTracker:allow-mouse-drag: * * Whether to allow dragging with mouse pointer. This should usually be * %FALSE. * * Since: 0.0.11 */ props[PROP_ALLOW_MOUSE_DRAG] = g_param_spec_boolean ("allow-mouse-drag", _("Allow mouse drag"), _("Whether to allow dragging with mouse pointer"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); g_object_class_install_properties (object_class, LAST_PROP, props); } static void hdy_swipe_tracker_init (HdySwipeTracker *self) { reset (self); self->orientation = GTK_ORIENTATION_HORIZONTAL; self->enabled = TRUE; } /** * hdy_swipe_tracker_new: * @widget: a #GtkWidget to add the tracker on * * Create a new #HdySwipeTracker object on @widget. * * Returns: the newly created #HdySwipeTracker object * * Since: 0.0.11 */ HdySwipeTracker * hdy_swipe_tracker_new (HdySwipeable *swipeable) { g_return_val_if_fail (swipeable != NULL, NULL); return g_object_new (HDY_TYPE_SWIPE_TRACKER, "swipeable", swipeable, NULL); } /** * hdy_swipe_tracker_get_enabled: * @self: a #HdySwipeTracker * * Get whether @self is enabled. When it's not enabled, no events will be * processed. Generally widgets will want to expose this via a property. * * Returns: %TRUE if @self is enabled * * Since: 0.0.11 */ gboolean hdy_swipe_tracker_get_enabled (HdySwipeTracker *self) { g_return_val_if_fail (HDY_IS_SWIPE_TRACKER (self), FALSE); return self->enabled; } /** * hdy_swipe_tracker_set_enabled: * @self: a #HdySwipeTracker * @enabled: whether to enable to swipe tracker * * Set whether @self is enabled. When it's not enabled, no events will be * processed. Usually widgets will want to expose this via a property. * * Since: 0.0.11 */ void hdy_swipe_tracker_set_enabled (HdySwipeTracker *self, gboolean enabled) { g_return_if_fail (HDY_IS_SWIPE_TRACKER (self)); enabled = !!enabled; if (self->enabled == enabled) return; self->enabled = enabled; if (!enabled && self->state != HDY_SWIPE_TRACKER_STATE_SCROLLING) reset (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ENABLED]); } /** * hdy_swipe_tracker_get_reversed: * @self: a #HdySwipeTracker * * Get whether @self is reversing the swipe direction. * * Returns: %TRUE is the direction is reversed * * Since: 0.0.11 */ gboolean hdy_swipe_tracker_get_reversed (HdySwipeTracker *self) { g_return_val_if_fail (HDY_IS_SWIPE_TRACKER (self), FALSE); return self->reversed; } /** * hdy_swipe_tracker_set_reversed: * @self: a #HdySwipeTracker * @reversed: whether to reverse the swipe direction * * Set whether to reverse the swipe direction. If @self is horizontal, * can be used for supporting RTL text direction. * * Since: 0.0.11 */ void hdy_swipe_tracker_set_reversed (HdySwipeTracker *self, gboolean reversed) { g_return_if_fail (HDY_IS_SWIPE_TRACKER (self)); reversed = !!reversed; if (self->reversed == reversed) return; self->reversed = reversed; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVERSED]); } /** * hdy_swipe_tracker_get_allow_mouse_drag: * @self: a #HdySwipeTracker * * Get whether @self can be dragged with mouse pointer. * * Returns: %TRUE is mouse dragging is allowed * * Since: 0.0.12 */ gboolean hdy_swipe_tracker_get_allow_mouse_drag (HdySwipeTracker *self) { g_return_val_if_fail (HDY_IS_SWIPE_TRACKER (self), FALSE); return self->allow_mouse_drag; } /** * hdy_swipe_tracker_set_allow_mouse_drag: * @self: a #HdySwipeTracker * @allow_mouse_drag: whether to allow mouse dragging * * Set whether @self can be dragged with mouse pointer. This should usually be * %FALSE. * * Since: 0.0.12 */ void hdy_swipe_tracker_set_allow_mouse_drag (HdySwipeTracker *self, gboolean allow_mouse_drag) { g_return_if_fail (HDY_IS_SWIPE_TRACKER (self)); allow_mouse_drag = !!allow_mouse_drag; if (self->allow_mouse_drag == allow_mouse_drag) return; self->allow_mouse_drag = allow_mouse_drag; if (self->touch_gesture) g_object_set (self->touch_gesture, "touch-only", !allow_mouse_drag, NULL); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ALLOW_MOUSE_DRAG]); } /** * hdy_swipe_tracker_captured_event: * @self: a #HdySwipeTracker * @event: a captured #GdkEvent * * Handles an event. This must be called for events received at capture phase * only. * * Returns: %TRUE is the event was handled and must not be propagated * * Since: 0.0.11 */ gboolean hdy_swipe_tracker_captured_event (HdySwipeTracker *self, GdkEvent *event) { GdkEventSequence *sequence; gboolean retval; GtkEventSequenceState state; g_return_val_if_fail (HDY_IS_SWIPE_TRACKER (self), GDK_EVENT_PROPAGATE); if (!self->enabled && self->state != HDY_SWIPE_TRACKER_STATE_SCROLLING) return GDK_EVENT_PROPAGATE; if (event->type == GDK_SCROLL) return captured_scroll_event (self, event); if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE && event->type != GDK_MOTION_NOTIFY && event->type != GDK_TOUCH_BEGIN && event->type != GDK_TOUCH_END && event->type != GDK_TOUCH_UPDATE && event->type != GDK_TOUCH_CANCEL) return GDK_EVENT_PROPAGATE; sequence = gdk_event_get_event_sequence (event); retval = gtk_event_controller_handle_event (GTK_EVENT_CONTROLLER (self->touch_gesture), event); state = gtk_gesture_get_sequence_state (self->touch_gesture, sequence); if (state == GTK_EVENT_SEQUENCE_DENIED) { gtk_event_controller_reset (GTK_EVENT_CONTROLLER (self->touch_gesture)); return GDK_EVENT_PROPAGATE; } if (self->state == HDY_SWIPE_TRACKER_STATE_SCROLLING) { return GDK_EVENT_STOP; } else if (self->state == HDY_SWIPE_TRACKER_STATE_FINISHING) { reset (self); return GDK_EVENT_STOP; } return retval; } static gboolean is_sorted (gdouble *array, gint n) { gint i; if (n < 2) return TRUE; for (i = 0; i < n - 1; i++) if (array[i] > array[i + 1]) return FALSE; return TRUE; } /** * hdy_swipe_tracker_confirm_swipe: * @self: a #HdySwipeTracker * @distance: swipe distance in pixels * @snap_points: (array length=n_snap_points) (transfer full): array of snap * points, must be sorted in ascending order * @n_snap_points: length of @snap_points * @current_progress: initial progress value * @cancel_progress: the value that will be used if the swipe is cancelled * * Confirms a swipe. User has to call this in #HdySwipeTracker::begin signal * handler, otherwise the swipe wouldn't start. If there's an animation running, * the user should stop it and pass its calue as @current_progress. * * The call is not a guarantee that the swipe will be started; child widgets * may intercept it, in which case #HdySwipeTracker::end will be emitted with * @cancel_progress value and 0 duration. * * @cancel_progress must always be a snap point, or an otherwise a value * matching a non-transient state, otherwise the widget will get stuck * mid-animation. * * If there's no animation running, @current_progress and @cancel_progress * must be same. * * Since: 0.0.11 */ void hdy_swipe_tracker_confirm_swipe (HdySwipeTracker *self, gdouble distance, gdouble *snap_points, gint n_snap_points, gdouble current_progress, gdouble cancel_progress) { g_return_if_fail (HDY_IS_SWIPE_TRACKER (self)); g_return_if_fail (distance > 0.0); g_return_if_fail (snap_points); g_return_if_fail (n_snap_points > 0); g_return_if_fail (is_sorted (snap_points, n_snap_points)); g_return_if_fail (current_progress >= snap_points[0]); g_return_if_fail (current_progress <= snap_points[n_snap_points - 1]); g_return_if_fail (cancel_progress >= snap_points[0]); g_return_if_fail (cancel_progress <= snap_points[n_snap_points - 1]); if (self->state != HDY_SWIPE_TRACKER_STATE_PREPARING) { gesture_cancel (self); return; } if (self->snap_points) g_free (self->snap_points); self->distance = distance; self->initial_progress = current_progress; self->progress = current_progress; self->velocity = 0; self->snap_points = snap_points; self->n_snap_points = n_snap_points; self->cancel_progress = cancel_progress; self->state = HDY_SWIPE_TRACKER_STATE_PENDING; } libhandy-0.0.13/src/hdy-swipeable-private.h000066400000000000000000000017411360136463700205460ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include G_BEGIN_DECLS void hdy_swipeable_switch_child (HdySwipeable *self, guint index, gint64 duration); void hdy_swipeable_begin_swipe (HdySwipeable *self, gint direction, gboolean direct); void hdy_swipeable_update_swipe (HdySwipeable *self, gdouble value); void hdy_swipeable_end_swipe (HdySwipeable *self, gint64 duration, gdouble to); void hdy_swipeable_emit_switch_child (HdySwipeable *self, guint index, gint64 duration); G_END_DECLS libhandy-0.0.13/src/hdy-swipeable.c000066400000000000000000000174431360136463700170770ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #include "hdy-swipeable-private.h" /** * SECTION:hdy-swipeable * @short_description: An interface for swipeable widgets. * @title: HdySwipeable * @See_also: #HdyLeaflet, #HdyPaginator, #HdySwipeGroup * * The #HdySwipeable interface is implemented by all swipeable widgets. They * can be synced using #HdySwipeGroup. * * #HdySwipeable is only meant to be used by libhandy widgets and is currently * implemented by #HdyLeaflet and #HdyPaginator. It should not be implemented * by applications. * * Since: 0.0.12 */ G_DEFINE_INTERFACE (HdySwipeable, hdy_swipeable, GTK_TYPE_WIDGET) enum { SIGNAL_SWITCH_CHILD, SIGNAL_BEGIN_SWIPE, SIGNAL_UPDATE_SWIPE, SIGNAL_END_SWIPE, SIGNAL_LAST_SIGNAL, }; static guint signals[SIGNAL_LAST_SIGNAL]; static void hdy_swipeable_default_init (HdySwipeableInterface *iface) { /** * HdySwipeable::switch-child: * @self: The #HdySwipeable instance * @index: the index of the child to switch to * @duration: Animation duration in milliseconds * * This signal should be emitted when the widget's visible child is changed. * * @duration can be 0 if the child is switched without animation. * * Since: 0.0.12 */ signals[SIGNAL_SWITCH_CHILD] = g_signal_new ("switch-child", G_TYPE_FROM_INTERFACE (iface), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_INT64); /** * HdySwipeable::begin-swipe: * @self: The #HdySwipeable instance * @direction: The direction of the swipe, can be 1 or -1 * * This signal is emitted when a possible swipe is detected. This is used by * #HdySwipeGroup, applications should not connect to it. * The @direction value can be used to restrict the swipe to a certain * direction. * * Since: 0.0.12 */ signals[SIGNAL_BEGIN_SWIPE] = g_signal_new ("begin-swipe", G_TYPE_FROM_INTERFACE (iface), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT); /** * HdySwipeable::update-swipe: * @self: The #HdySwipeable instance * @value: The current animation progress value * * This signal is emitted every time the progress value changes. This is used * by #HdySwipeGroup, applications should not connect to it. * * Since: 0.0.12 */ signals[SIGNAL_UPDATE_SWIPE] = g_signal_new ("update-swipe", G_TYPE_FROM_INTERFACE (iface), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_DOUBLE); /** * HdySwipeable::end-swipe: * @self: The #HdySwipeable instance * @duration: Snap-back animation duration in milliseconds * @to: The progress value to animate to * * This signal is emitted as soon as the gesture has stopped. This is used by * #HdySwipeGroup, applications should not connect to it. * * Since: 0.0.12 */ signals[SIGNAL_END_SWIPE] = g_signal_new ("end-swipe", G_TYPE_FROM_INTERFACE (iface), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_INT64, G_TYPE_DOUBLE); } /** * hdy_swipeable_switch_child: * @self: a #HdySwipeable * @index: the index of the child to switch to * @duration: Animation duration in milliseconds * * See HdySwipeable::switch-child. * * Since: 0.0.12 */ void hdy_swipeable_switch_child (HdySwipeable *self, guint index, gint64 duration) { HdySwipeableInterface *iface; g_return_if_fail (HDY_IS_SWIPEABLE (self)); iface = HDY_SWIPEABLE_GET_IFACE (self); g_return_if_fail (iface->switch_child != NULL); (* iface->switch_child) (self, index, duration); } /** * hdy_swipeable_begin_swipe: * @self: a #HdySwipeable * @direction: The direction of the swipe, can be 1 or -1 * @direct: %TRUE if the swipe is directly triggered by a gesture, * %FALSE if it's triggered via a #HdySwipeGroup * * This function is called by #HdySwipeTracker when a possible swipe is detected. * The implementation should check whether a swipe is possible, and if it is, * it must call hdy_swipe_tracker_confirm_swipe() to provide details about the * swipe, see that function for details. * The @direction value can be used to restrict the swipe to a certain direction. * * The @direct parameter can be used to have widgets that aren't swipeable, but * can still animate in sync with other widgets in a #HdySwipeGroup by only * applying restrictions if @direct is %TRUE. * * Since: 0.0.12 */ void hdy_swipeable_begin_swipe (HdySwipeable *self, gint direction, gboolean direct) { HdySwipeableInterface *iface; g_return_if_fail (HDY_IS_SWIPEABLE (self)); iface = HDY_SWIPEABLE_GET_IFACE (self); g_return_if_fail (iface->begin_swipe != NULL); (* iface->begin_swipe) (self, direction, direct); g_signal_emit (self, signals[SIGNAL_BEGIN_SWIPE], 0, direction); } /** * hdy_swipeable_update_swipe: * @self: a #HdySwipeable * @value: The current animation progress value * * This function is called by #HdySwipeTracker every time the progress value * changes. The widget must redraw the widget to reflect the change. * * Since: 0.0.12 */ void hdy_swipeable_update_swipe (HdySwipeable *self, gdouble value) { HdySwipeableInterface *iface; g_return_if_fail (HDY_IS_SWIPEABLE (self)); iface = HDY_SWIPEABLE_GET_IFACE (self); g_return_if_fail (iface->update_swipe != NULL); (* iface->update_swipe) (self, value); g_signal_emit (self, signals[SIGNAL_UPDATE_SWIPE], 0, value); } /** * hdy_swipeable_end_swipe: * @self: a #HdySwipeable * @duration: Snap-back animation duration in milliseconds * @to: The progress value to animate to * * This function is called by #HdySwipeTracker as soon as the gesture has * stopped. The widget must animate the progress from the current value to the * @to value with easeOutCubic interpolation over the next @duration * milliseconds. * * @to will always match either one of the provided snap points if the swipe was * completed successfully, or @cancel_progress value passed in * hdy_swipe_tracker_confirm_swipe() call if the swipe was cancelled. * * @duration can be 0, in that case the widget must immediately set the * progress value to @to. * * Since: 0.0.12 */ void hdy_swipeable_end_swipe (HdySwipeable *self, gint64 duration, gdouble to) { HdySwipeableInterface *iface; g_return_if_fail (HDY_IS_SWIPEABLE (self)); iface = HDY_SWIPEABLE_GET_IFACE (self); g_return_if_fail (iface->end_swipe != NULL); (* iface->end_swipe) (self, duration, to); g_signal_emit (self, signals[SIGNAL_END_SWIPE], 0, duration, to); } /** * hdy_swipeable_emit_switch_child: * @self: a #HdySwipeable * @index: the index of the child to switch to * @duration: Animation duration in milliseconds * * Emits HdySwipeable::switch-child signal. This should be called when the * widget switches visible child widget. * * @duration can be 0 if the child is switched without animation. * * Since: 0.0.12 */ void hdy_swipeable_emit_switch_child (HdySwipeable *self, guint index, gint64 duration) { g_return_if_fail (HDY_IS_SWIPEABLE (self)); g_signal_emit (self, signals[SIGNAL_SWITCH_CHILD], 0, index, duration); } libhandy-0.0.13/src/hdy-swipeable.h000066400000000000000000000022301360136463700170700ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define HDY_TYPE_SWIPEABLE (hdy_swipeable_get_type ()) G_DECLARE_INTERFACE (HdySwipeable, hdy_swipeable, HDY, SWIPEABLE, GtkWidget) /** * HdySwipeableInterface: * @parent: The parent interface. * @switch_child: Switches visible child. * @begin_swipe: Starts a swipe gesture. * @update_swipe: Updates swipe progress value. * @end_swipe: Ends a swipe gesture. * * An interface for swipeable widgets. * * Since: 0.0.12 **/ struct _HdySwipeableInterface { GTypeInterface parent; void (*switch_child) (HdySwipeable *self, guint index, gint64 duration); void (*begin_swipe) (HdySwipeable *self, gint direction, gboolean direct); void (*update_swipe) (HdySwipeable *self, gdouble value); void (*end_swipe) (HdySwipeable *self, gint64 duration, gdouble to); }; G_END_DECLS libhandy-0.0.13/src/hdy-text.css000066400000000000000000000002561360136463700164500ustar00rootroot00000000000000/* Implement text styles. Add them with the fallback priority instead of * hardcoding the style so themes can use their own style instead. */ .h4 { font-weight: bold; } libhandy-0.0.13/src/hdy-title-bar.c000066400000000000000000000134421360136463700170020ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "hdy-title-bar.h" #include /** * SECTION:hdy-title-bar * @short_description: A simple title bar container. * @Title: HdyTitleBar * * HdyTitleBar is meant to be used as the top-level widget of your window's * title bar. It will be drawn with the same style as a GtkHeaderBar but it * won't force a widget layout on you: you can put whatever widget you want in * it, including a GtkHeaderBar. * * HdyTitleBar becomes really useful when you want to animate header bars, like * an adaptive application using #HdyLeaflet would do. */ enum { PROP_0, PROP_SELECTION_MODE, LAST_PROP, }; struct _HdyTitleBar { GtkBin parent_instance; gboolean selection_mode; }; G_DEFINE_TYPE (HdyTitleBar, hdy_title_bar, GTK_TYPE_BIN) static GParamSpec *props[LAST_PROP]; /** * hdy_title_bar_set_selection_mode: * @self: a #HdyTitleBar * @selection_mode: %TRUE to enable the selection mode * * Sets whether @self is in selection mode. */ void hdy_title_bar_set_selection_mode (HdyTitleBar *self, gboolean selection_mode) { GtkStyleContext *context; g_return_if_fail (HDY_IS_TITLE_BAR (self)); selection_mode = !!selection_mode; context = gtk_widget_get_style_context (GTK_WIDGET (self)); if (self->selection_mode == selection_mode) return; self->selection_mode = selection_mode; if (selection_mode) gtk_style_context_add_class (context, "selection-mode"); else gtk_style_context_remove_class (context, "selection-mode"); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTION_MODE]); } /** * hdy_title_bar_get_selection_mode: * @self: a #HdyTitleBar * * Returns wether whether @self is in selection mode. * * Returns: %TRUE if the title bar is in selection mode */ gboolean hdy_title_bar_get_selection_mode (HdyTitleBar *self) { g_return_val_if_fail (HDY_IS_TITLE_BAR (self), FALSE); return self->selection_mode; } static void style_updated_cb (HdyTitleBar *self) { GtkStyleContext *context; gboolean selection_mode; g_assert (HDY_IS_TITLE_BAR (self)); context = gtk_widget_get_style_context (GTK_WIDGET (self)); selection_mode = gtk_style_context_has_class (context, "selection-mode"); if (self->selection_mode == selection_mode) return; self->selection_mode = selection_mode; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTION_MODE]); } static void hdy_title_bar_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyTitleBar *self = HDY_TITLE_BAR (object); switch (prop_id) { case PROP_SELECTION_MODE: g_value_set_boolean (value, hdy_title_bar_get_selection_mode (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_title_bar_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyTitleBar *self = HDY_TITLE_BAR (object); switch (prop_id) { case PROP_SELECTION_MODE: hdy_title_bar_set_selection_mode (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static gboolean hdy_title_bar_draw (GtkWidget *widget, cairo_t *cr) { GtkStyleContext *context; context = gtk_widget_get_style_context (widget); /* GtkWidget draws nothing by default so we have to render the background * explicitely for HdyTitleBar to render the typical titlebar background. */ gtk_render_background (context, cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget)); return GTK_WIDGET_CLASS (hdy_title_bar_parent_class)->draw (widget, cr); } static void hdy_title_bar_class_init (HdyTitleBarClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->get_property = hdy_title_bar_get_property; object_class->set_property = hdy_title_bar_set_property; widget_class->draw = hdy_title_bar_draw; /** * HdyTitleBar:selection_mode: * * %TRUE if the title bar is in selection mode. */ props[PROP_SELECTION_MODE] = g_param_spec_boolean ("selection-mode", _("Selection mode"), _("Whether or not the title bar is in selection mode"), FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_TITLE_BAR); /* Adwaita states it expects a headerbar to be the top-level titlebar widget, * so style-wise HdyTitleBar pretends to be one as its role is to be the * top-level titlebar widget. */ gtk_widget_class_set_css_name (widget_class, "headerbar"); gtk_container_class_handle_border_width (container_class); } static void hdy_title_bar_init (HdyTitleBar *self) { GtkStyleContext *context; context = gtk_widget_get_style_context (GTK_WIDGET (self)); /* Ensure the widget has the titlebar style class. */ gtk_style_context_add_class (context, "titlebar"); g_signal_connect (self, "style-updated", G_CALLBACK (style_updated_cb), NULL); } /** * hdy_title_bar_new: * * Creates a new #HdyTitleBar. * * Returns: a new #HdyTitleBar */ HdyTitleBar * hdy_title_bar_new (void) { return g_object_new (HDY_TYPE_TITLE_BAR, NULL); } libhandy-0.0.13/src/hdy-title-bar.h000066400000000000000000000011571360136463700170070ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_TITLE_BAR (hdy_title_bar_get_type()) G_DECLARE_FINAL_TYPE (HdyTitleBar, hdy_title_bar, HDY, TITLE_BAR, GtkBin) HdyTitleBar *hdy_title_bar_new (void); gboolean hdy_title_bar_get_selection_mode (HdyTitleBar *self); void hdy_title_bar_set_selection_mode (HdyTitleBar *self, gboolean selection_mode); G_END_DECLS libhandy-0.0.13/src/hdy-value-object.c000066400000000000000000000140121360136463700174710ustar00rootroot00000000000000/* * Copyright (C) 2019 Red Hat Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "hdy-value-object.h" /** * SECTION:hdy-value-object * @short_description: An object representing a #GValue. * @Title: HdyValueObject * * The #HdyValueObject object represents a #GValue, allowing it to be * used with #GListModel. * * Since: 0.0.8 */ struct _HdyValueObject { GObject parent_instance; GValue value; }; G_DEFINE_TYPE (HdyValueObject, hdy_value_object, G_TYPE_OBJECT) enum { PROP_0, PROP_VALUE, N_PROPS }; static GParamSpec *props [N_PROPS]; /** * hdy_value_object_new: * @value: the #GValue to store * * Create a new #HdyValueObject. * * Returns: a new #HdyValueObject * Since: 0.0.8 */ HdyValueObject * hdy_value_object_new (const GValue *value) { return g_object_new (HDY_TYPE_VALUE_OBJECT, "value", value, NULL); } /** * hdy_value_object_new_collect: (skip) * @type: the #GType of the value * @...: the value to store * * Creates a new #HdyValueObject. This is a convenience method which uses * the G_VALUE_COLLECT() macro internally. * * Returns: a new #HdyValueObject * Since: 0.0.8 */ HdyValueObject* hdy_value_object_new_collect (GType type, ...) { g_auto(GValue) value = G_VALUE_INIT; g_autofree gchar *error = NULL; va_list var_args; va_start (var_args, type); G_VALUE_COLLECT_INIT (&value, type, var_args, 0, &error); va_end (var_args); if (error) g_critical ("%s: %s", G_STRFUNC, error); return g_object_new (HDY_TYPE_VALUE_OBJECT, "value", &value, NULL); } /** * hdy_value_object_new_string: (skip) * @string: (transfer none): the string to store * * Creates a new #HdyValueObject. This is a convenience method to create a * #HdyValueObject that stores a string. * * Returns: a new #HdyValueObject * Since: 0.0.8 */ HdyValueObject* hdy_value_object_new_string (const gchar *string) { g_auto(GValue) value = G_VALUE_INIT; g_value_init (&value, G_TYPE_STRING); g_value_set_string (&value, string); return hdy_value_object_new (&value); } /** * hdy_value_object_new_take_string: (skip) * @string: (transfer full): the string to store * * Creates a new #HdyValueObject. This is a convenience method to create a * #HdyValueObject that stores a string taking ownership of it. * * Returns: a new #HdyValueObject * Since: 0.0.8 */ HdyValueObject* hdy_value_object_new_take_string (gchar *string) { g_auto(GValue) value = G_VALUE_INIT; g_value_init (&value, G_TYPE_STRING); g_value_take_string (&value, string); return hdy_value_object_new (&value); } static void hdy_value_object_finalize (GObject *object) { HdyValueObject *self = HDY_VALUE_OBJECT (object); g_value_unset (&self->value); G_OBJECT_CLASS (hdy_value_object_parent_class)->finalize (object); } static void hdy_value_object_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyValueObject *self = HDY_VALUE_OBJECT (object); switch (prop_id) { case PROP_VALUE: g_value_set_boxed (value, &self->value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_value_object_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyValueObject *self = HDY_VALUE_OBJECT (object); GValue *real_value; switch (prop_id) { case PROP_VALUE: /* construct only */ real_value = g_value_get_boxed (value); g_value_init (&self->value, real_value->g_type); g_value_copy (real_value, &self->value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void hdy_value_object_class_init (HdyValueObjectClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = hdy_value_object_finalize; object_class->get_property = hdy_value_object_get_property; object_class->set_property = hdy_value_object_set_property; props[PROP_VALUE] = g_param_spec_boxed ("value", C_("HdyValueObjectClass", "Value"), C_("HdyValueObjectClass", "The contained value"), G_TYPE_VALUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, props); } static void hdy_value_object_init (HdyValueObject *self) { } /** * hdy_value_object_get_value: * @value: the #HdyValueObject * * Return the contained value. * * Returns: (transfer none): the contained #GValue * Since: 0.0.8 */ const GValue* hdy_value_object_get_value (HdyValueObject *value) { return &value->value; } /** * hdy_value_object_copy_value: * @value: the #HdyValueObject * @dest: #GValue with correct type to copy into * * Copy data from the contained #GValue into @dest. * * Since: 0.0.8 */ void hdy_value_object_copy_value (HdyValueObject *value, GValue *dest) { g_value_copy (&value->value, dest); } /** * hdy_value_object_get_string: * @value: the #HdyValueObject * * Returns the contained string if the value is of type #G_TYPE_STRING. * * Returns: (transfer none): the contained string * Since: 0.0.8 */ const gchar* hdy_value_object_get_string (HdyValueObject *value) { return g_value_get_string (&value->value); } /** * hdy_value_object_dup_string: * @value: the #HdyValueObject * * Returns a copy of the contained string if the value is of type * #G_TYPE_STRING. * * Returns: (transfer full): a copy of the contained string * Since: 0.0.8 */ gchar* hdy_value_object_dup_string (HdyValueObject *value) { return g_value_dup_string (&value->value); } libhandy-0.0.13/src/hdy-value-object.h000066400000000000000000000021441360136463700175010ustar00rootroot00000000000000/* * Copyright (C) 2019 Red Hat Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include G_BEGIN_DECLS #define HDY_TYPE_VALUE_OBJECT (hdy_value_object_get_type()) G_DECLARE_FINAL_TYPE (HdyValueObject, hdy_value_object, HDY, VALUE_OBJECT, GObject) HdyValueObject *hdy_value_object_new (const GValue *value); HdyValueObject *hdy_value_object_new_collect (GType type, ...); HdyValueObject *hdy_value_object_new_string (const gchar *string); HdyValueObject *hdy_value_object_new_take_string (gchar *string); const GValue* hdy_value_object_get_value (HdyValueObject *value); void hdy_value_object_copy_value (HdyValueObject *value, GValue *dest); const gchar* hdy_value_object_get_string (HdyValueObject *value); gchar* hdy_value_object_dup_string (HdyValueObject *value); G_END_DECLS libhandy-0.0.13/src/hdy-version.h.in000066400000000000000000000037021360136463700172140ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif /** * SECTION:hdy-version * @short_description: Handy version checking. * * Handy provides macros to check the version of the library at compile-time. */ /** * HDY_MAJOR_VERSION: * * Hdy major version component (e.g. 1 if %HDY_VERSION is 1.2.3) */ #define HDY_MAJOR_VERSION (@HDY_MAJOR_VERSION@) /** * HDY_MINOR_VERSION: * * Hdy minor version component (e.g. 2 if %HDY_VERSION is 1.2.3) */ #define HDY_MINOR_VERSION (@HDY_MINOR_VERSION@) /** * HDY_MICRO_VERSION: * * Hdy micro version component (e.g. 3 if %HDY_VERSION is 1.2.3) */ #define HDY_MICRO_VERSION (@HDY_MICRO_VERSION@) /** * HDY_VERSION * * Hdy version. */ #define HDY_VERSION (@HDY_VERSION@) /** * HDY_VERSION_S: * * Handy version, encoded as a string, useful for printing and * concatenation. */ #define HDY_VERSION_S "@HDY_VERSION@" #define HDY_ENCODE_VERSION(major,minor,micro) \ ((major) << 24 | (minor) << 16 | (micro) << 8) /** * HDY_VERSION_HEX: * * Handy version, encoded as an hexadecimal number, useful for * integer comparisons. */ #define HDY_VERSION_HEX \ (HDY_ENCODE_VERSION (HDY_MAJOR_VERSION, HDY_MINOR_VERSION, HDY_MICRO_VERSION)) /** * HDY_CHECK_VERSION: * @major: required major version * @minor: required minor version * @micro: required micro version * * Compile-time version checking. Evaluates to %TRUE if the version * of handy is greater than the required one. */ #define HDY_CHECK_VERSION(major,minor,micro) \ (HDY_MAJOR_VERSION > (major) || \ (HDY_MAJOR_VERSION == (major) && HDY_MINOR_VERSION > (minor)) || \ (HDY_MAJOR_VERSION == (major) && HDY_MINOR_VERSION == (minor) && \ HDY_MICRO_VERSION >= (micro))) libhandy-0.0.13/src/hdy-view-switcher-bar-box.css000066400000000000000000000001001360136463700216000ustar00rootroot00000000000000hdyviewswitcherbar actionbar > revealer > box { padding: 0; } libhandy-0.0.13/src/hdy-view-switcher-bar.c000066400000000000000000000263721360136463700204670ustar00rootroot00000000000000/* * Copyright (C) 2019 Zander Brown * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-enums.h" #include "hdy-style-private.h" #include "hdy-view-switcher-bar.h" /** * SECTION:hdy-view-switcher-bar * @short_description: An view switcher action bar. * @title: HdyViewSwitcherBar * * An action bar letting you switch between multiple views offered by a * #GtkStack, via an #HdyViewSwitcher. It is designed to be put at the bottom of * a window and to be revealed only on really narrow windows e.g. on mobile * phones. * * Since: 0.0.10 */ enum { PROP_0, PROP_POLICY, PROP_ICON_SIZE, PROP_STACK, PROP_REVEAL, LAST_PROP, }; typedef struct { GtkActionBar *action_bar; GtkRevealer *revealer; HdyViewSwitcher *view_switcher; HdyViewSwitcherPolicy policy; GtkIconSize icon_size; gboolean reveal; } HdyViewSwitcherBarPrivate; static GParamSpec *props[LAST_PROP]; G_DEFINE_TYPE_WITH_CODE (HdyViewSwitcherBar, hdy_view_switcher_bar, GTK_TYPE_BIN, G_ADD_PRIVATE (HdyViewSwitcherBar)) static void hdy_view_switcher_bar_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyViewSwitcherBar *self = HDY_VIEW_SWITCHER_BAR (object); switch (prop_id) { case PROP_POLICY: g_value_set_enum (value, hdy_view_switcher_bar_get_policy (self)); break; case PROP_ICON_SIZE: g_value_set_int (value, hdy_view_switcher_bar_get_icon_size (self)); break; case PROP_STACK: g_value_set_object (value, hdy_view_switcher_bar_get_stack (self)); break; case PROP_REVEAL: g_value_set_boolean (value, hdy_view_switcher_bar_get_reveal (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void hdy_view_switcher_bar_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyViewSwitcherBar *self = HDY_VIEW_SWITCHER_BAR (object); switch (prop_id) { case PROP_POLICY: hdy_view_switcher_bar_set_policy (self, g_value_get_enum (value)); break; case PROP_ICON_SIZE: hdy_view_switcher_bar_set_icon_size (self, g_value_get_int (value)); break; case PROP_STACK: hdy_view_switcher_bar_set_stack (self, g_value_get_object (value)); break; case PROP_REVEAL: hdy_view_switcher_bar_set_reveal (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void hdy_view_switcher_bar_class_init (HdyViewSwitcherBarClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->get_property = hdy_view_switcher_bar_get_property; object_class->set_property = hdy_view_switcher_bar_set_property; /** * HdyViewSwitcherBar:policy: * * The #HdyViewSwitcherPolicy the #HdyViewSwitcher should use to determine * which mode to use. * * Since: 0.0.10 */ props[PROP_POLICY] = g_param_spec_enum ("policy", _("Policy"), _("The policy to determine the mode to use"), HDY_TYPE_VIEW_SWITCHER_POLICY, HDY_VIEW_SWITCHER_POLICY_NARROW, G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * HdyViewSwitcherBar:icon-size: * * Use the "icon-size" property to hint the icons to use, you almost certainly * want to leave this as %GTK_ICON_SIZE_BUTTON. * * Since: 0.0.10 */ props[PROP_ICON_SIZE] = g_param_spec_int ("icon-size", _("Icon Size"), _("Symbolic size to use for named icon"), 0, G_MAXINT, GTK_ICON_SIZE_BUTTON, G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * HdyViewSwitcherBar:stack: * * The #GtkStack the #HdyViewSwitcher controls. * * Since: 0.0.10 */ props[PROP_STACK] = g_param_spec_object ("stack", _("Stack"), _("Stack"), GTK_TYPE_STACK, G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * HdyViewSwitcherBar:reveal: * * Whether the bar should be revealed or hidden. * * Since: 0.0.10 */ props[PROP_REVEAL] = g_param_spec_boolean ("reveal", _("Reveal"), _("Whether the view switcher is revealed"), FALSE, G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_css_name (widget_class, "hdyviewswitcherbar"); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-view-switcher-bar.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherBar, action_bar); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherBar, view_switcher); } static void hdy_view_switcher_bar_init (HdyViewSwitcherBar *self) { HdyViewSwitcherBarPrivate *priv; g_autoptr (GtkCssProvider) provider = gtk_css_provider_new (); GtkWidget *box; priv = hdy_view_switcher_bar_get_instance_private (self); /* This must be initialized before the template so the embedded view switcher * can pick up the correct default value. */ priv->policy = HDY_VIEW_SWITCHER_POLICY_NARROW; priv->icon_size = GTK_ICON_SIZE_BUTTON; gtk_widget_init_template (GTK_WIDGET (self)); priv->revealer = GTK_REVEALER (gtk_bin_get_child (GTK_BIN (priv->action_bar))); g_object_bind_property (self, "reveal", priv->revealer, "reveal-child", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); gtk_revealer_set_transition_type (priv->revealer, GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP); box = gtk_bin_get_child (GTK_BIN (priv->revealer)); gtk_css_provider_load_from_resource (provider, "/sm/puri/handy/style/hdy-view-switcher-bar-box.css"); gtk_style_context_add_provider (gtk_widget_get_style_context (box), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); } /** * hdy_view_switcher_bar_new: * * Creates a new #HdyViewSwitcherBar widget. * * Returns: a new #HdyViewSwitcherBar * * Since: 0.0.10 */ HdyViewSwitcherBar * hdy_view_switcher_bar_new (void) { return g_object_new (HDY_TYPE_VIEW_SWITCHER_BAR, NULL); } /** * hdy_view_switcher_bar_get_policy: * @self: a #HdyViewSwitcherBar * * Gets the policy of @self. * * Returns: the policy of @self * * Since: 0.0.10 */ HdyViewSwitcherPolicy hdy_view_switcher_bar_get_policy (HdyViewSwitcherBar *self) { HdyViewSwitcherBarPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_BAR (self), HDY_VIEW_SWITCHER_POLICY_NARROW); priv = hdy_view_switcher_bar_get_instance_private (self); return priv->policy; } /** * hdy_view_switcher_bar_set_policy: * @self: a #HdyViewSwitcherBar * @policy: the new policy * * Sets the policy of @self. * * Since: 0.0.10 */ void hdy_view_switcher_bar_set_policy (HdyViewSwitcherBar *self, HdyViewSwitcherPolicy policy) { HdyViewSwitcherBarPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BAR (self)); priv = hdy_view_switcher_bar_get_instance_private (self); if (priv->policy == policy) return; priv->policy = policy; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POLICY]); gtk_widget_queue_resize (GTK_WIDGET (self)); } /** * hdy_view_switcher_bar_get_icon_size: * @self: a #HdyViewSwitcherBar * * Get the icon size of the images used in the #HdyViewSwitcher. * * Returns: the icon size of the images * * Since: 0.0.10 */ GtkIconSize hdy_view_switcher_bar_get_icon_size (HdyViewSwitcherBar *self) { HdyViewSwitcherBarPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_BAR (self), GTK_ICON_SIZE_BUTTON); priv = hdy_view_switcher_bar_get_instance_private (self); return priv->icon_size; } /** * hdy_view_switcher_bar_set_icon_size: * @self: a #HdyViewSwitcherBar * @icon_size: the new icon size * * Change the icon size hint for the icons in a #HdyViewSwitcher. * * Since: 0.0.10 */ void hdy_view_switcher_bar_set_icon_size (HdyViewSwitcherBar *self, GtkIconSize icon_size) { HdyViewSwitcherBarPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BAR (self)); priv = hdy_view_switcher_bar_get_instance_private (self); if (priv->icon_size == icon_size) return; priv->icon_size = icon_size; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_SIZE]); } /** * hdy_view_switcher_bar_get_stack: * @self: a #HdyViewSwitcherBar * * Get the #GtkStack being controlled by the #HdyViewSwitcher. * * Returns: (nullable) (transfer none): the #GtkStack, or %NULL if none has been set * * Since: 0.0.10 */ GtkStack * hdy_view_switcher_bar_get_stack (HdyViewSwitcherBar *self) { HdyViewSwitcherBarPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_BAR (self), NULL); priv = hdy_view_switcher_bar_get_instance_private (self); return hdy_view_switcher_get_stack (priv->view_switcher); } /** * hdy_view_switcher_bar_set_stack: * @self: a #HdyViewSwitcherBar * @stack: (nullable): a #GtkStack * * Sets the #GtkStack to control. * * Since: 0.0.10 */ void hdy_view_switcher_bar_set_stack (HdyViewSwitcherBar *self, GtkStack *stack) { HdyViewSwitcherBarPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BAR (self)); g_return_if_fail (stack == NULL || GTK_IS_STACK (stack)); priv = hdy_view_switcher_bar_get_instance_private (self); if (hdy_view_switcher_get_stack (priv->view_switcher) == stack) return; hdy_view_switcher_set_stack (priv->view_switcher, stack); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STACK]); } /** * hdy_view_switcher_bar_get_reveal: * @self: a #HdyViewSwitcherBar * * Gets whether @self should be revealed or not. * * Returns: %TRUE if @self is revealed, %FALSE if not. * * Since: 0.0.10 */ gboolean hdy_view_switcher_bar_get_reveal (HdyViewSwitcherBar *self) { HdyViewSwitcherBarPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_BAR (self), FALSE); priv = hdy_view_switcher_bar_get_instance_private (self); return priv->reveal; } /** * hdy_view_switcher_bar_set_reveal: * @self: a #HdyViewSwitcherBar * @reveal: %TRUE to reveal @self * * Sets whether @self should be revealed or not. * * Since: 0.0.10 */ void hdy_view_switcher_bar_set_reveal (HdyViewSwitcherBar *self, gboolean reveal) { HdyViewSwitcherBarPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BAR (self)); priv = hdy_view_switcher_bar_get_instance_private (self); reveal = !!reveal; if (priv->reveal == reveal) return; priv->reveal = reveal; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEAL]); } libhandy-0.0.13/src/hdy-view-switcher-bar.h000066400000000000000000000030231360136463700204600ustar00rootroot00000000000000/* * Copyright (C) 2019 Zander Brown * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include #include "hdy-view-switcher.h" G_BEGIN_DECLS #define HDY_TYPE_VIEW_SWITCHER_BAR (hdy_view_switcher_bar_get_type()) struct _HdyViewSwitcherBarClass { GtkBinClass parent_class; }; G_DECLARE_DERIVABLE_TYPE (HdyViewSwitcherBar, hdy_view_switcher_bar, HDY, VIEW_SWITCHER_BAR, GtkBin) HdyViewSwitcherBar *hdy_view_switcher_bar_new (void); HdyViewSwitcherPolicy hdy_view_switcher_bar_get_policy (HdyViewSwitcherBar *self); void hdy_view_switcher_bar_set_policy (HdyViewSwitcherBar *self, HdyViewSwitcherPolicy policy); GtkIconSize hdy_view_switcher_bar_get_icon_size (HdyViewSwitcherBar *self); void hdy_view_switcher_bar_set_icon_size (HdyViewSwitcherBar *self, GtkIconSize icon_size); GtkStack *hdy_view_switcher_bar_get_stack (HdyViewSwitcherBar *self); void hdy_view_switcher_bar_set_stack (HdyViewSwitcherBar *self, GtkStack *stack); gboolean hdy_view_switcher_bar_get_reveal (HdyViewSwitcherBar *self); void hdy_view_switcher_bar_set_reveal (HdyViewSwitcherBar *self, gboolean reveal); G_END_DECLS libhandy-0.0.13/src/hdy-view-switcher-bar.ui000066400000000000000000000016561360136463700206600ustar00rootroot00000000000000 libhandy-0.0.13/src/hdy-view-switcher-button-private.h000066400000000000000000000042261360136463700227050ustar00rootroot00000000000000/* * Copyright (C) 2019 Zander Brown * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_VIEW_SWITCHER_BUTTON (hdy_view_switcher_button_get_type()) struct _HdyViewSwitcherButtonClass { GtkRadioButtonClass parent_class; }; G_DECLARE_DERIVABLE_TYPE (HdyViewSwitcherButton, hdy_view_switcher_button, HDY, VIEW_SWITCHER_BUTTON, GtkRadioButton) HdyViewSwitcherButton *hdy_view_switcher_button_new (void); const gchar *hdy_view_switcher_button_get_icon_name (HdyViewSwitcherButton *self); void hdy_view_switcher_button_set_icon_name (HdyViewSwitcherButton *self, const gchar *icon_name); GtkIconSize hdy_view_switcher_button_get_icon_size (HdyViewSwitcherButton *self); void hdy_view_switcher_button_set_icon_size (HdyViewSwitcherButton *self, GtkIconSize icon_size); gboolean hdy_view_switcher_button_get_needs_attention (HdyViewSwitcherButton *self); void hdy_view_switcher_button_set_needs_attention (HdyViewSwitcherButton *self, gboolean needs_attention); const gchar *hdy_view_switcher_button_get_label (HdyViewSwitcherButton *self); void hdy_view_switcher_button_set_label (HdyViewSwitcherButton *self, const gchar *label); void hdy_view_switcher_button_set_narrow_ellipsize (HdyViewSwitcherButton *self, PangoEllipsizeMode mode); void hdy_view_switcher_button_get_size (HdyViewSwitcherButton *self, gint *h_min_width, gint *h_nat_width, gint *v_min_width, gint *v_nat_width); G_END_DECLS libhandy-0.0.13/src/hdy-view-switcher-button.c000066400000000000000000000505771360136463700212420ustar00rootroot00000000000000/* * Copyright (C) 2019 Zander Brown * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-style-private.h" #include "hdy-view-switcher-button-private.h" /** * PRIVATE:hdy-view-switcher-button * @short_description: Button used in #HdyViewSwitcher. * @title: HdyViewSwitcherButton * @See_also: #HdyViewSwitcher * @stability: Private * * #HdyViewSwitcherButton represents an application's view. It is designed to be * used exclusively internally by #HdyViewSwitcher. * * Since: 0.0.10 */ enum { PROP_0, PROP_ICON_SIZE, PROP_ICON_NAME, PROP_NEEDS_ATTENTION, /* Overridden properties */ PROP_LABEL, PROP_ORIENTATION, LAST_PROP = PROP_NEEDS_ATTENTION + 1, }; typedef struct { GtkBox *horizontal_box; GtkImage *horizontal_image; GtkLabel *horizontal_label_active; GtkLabel *horizontal_label_inactive; GtkStack *horizontal_label_stack; GtkStack *stack; GtkBox *vertical_box; GtkImage *vertical_image; GtkLabel *vertical_label_active; GtkLabel *vertical_label_inactive; GtkStack *vertical_label_stack; gchar *icon_name; GtkIconSize icon_size; gchar *label; GtkOrientation orientation; } HdyViewSwitcherButtonPrivate; static GParamSpec *props[LAST_PROP]; G_DEFINE_TYPE_WITH_CODE (HdyViewSwitcherButton, hdy_view_switcher_button, GTK_TYPE_RADIO_BUTTON, G_ADD_PRIVATE (HdyViewSwitcherButton) G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)) static void on_active_changed (HdyViewSwitcherButton *self) { HdyViewSwitcherButtonPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self)); priv = hdy_view_switcher_button_get_instance_private (self); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self))) { gtk_stack_set_visible_child (priv->horizontal_label_stack, GTK_WIDGET (priv->horizontal_label_active)); gtk_stack_set_visible_child (priv->vertical_label_stack, GTK_WIDGET (priv->vertical_label_active)); } else { gtk_stack_set_visible_child (priv->horizontal_label_stack, GTK_WIDGET (priv->horizontal_label_inactive)); gtk_stack_set_visible_child (priv->vertical_label_stack, GTK_WIDGET (priv->vertical_label_inactive)); } } static GtkOrientation get_orientation (HdyViewSwitcherButton *self) { HdyViewSwitcherButtonPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self), GTK_ORIENTATION_HORIZONTAL); priv = hdy_view_switcher_button_get_instance_private (self); return priv->orientation; } static void set_orientation (HdyViewSwitcherButton *self, GtkOrientation orientation) { HdyViewSwitcherButtonPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self)); priv = hdy_view_switcher_button_get_instance_private (self); if (priv->orientation == orientation) return; priv->orientation = orientation; gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (priv->orientation == GTK_ORIENTATION_VERTICAL ? priv->vertical_box : priv->horizontal_box)); } static void hdy_view_switcher_button_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyViewSwitcherButton *self = HDY_VIEW_SWITCHER_BUTTON (object); switch (prop_id) { case PROP_ICON_NAME: g_value_set_string (value, hdy_view_switcher_button_get_icon_name (self)); break; case PROP_ICON_SIZE: g_value_set_int (value, hdy_view_switcher_button_get_icon_size (self)); break; case PROP_NEEDS_ATTENTION: g_value_set_boolean (value, hdy_view_switcher_button_get_needs_attention (self)); break; case PROP_LABEL: g_value_set_string (value, hdy_view_switcher_button_get_label (self)); break; case PROP_ORIENTATION: g_value_set_enum (value, get_orientation (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void hdy_view_switcher_button_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyViewSwitcherButton *self = HDY_VIEW_SWITCHER_BUTTON (object); switch (prop_id) { case PROP_ICON_NAME: hdy_view_switcher_button_set_icon_name (self, g_value_get_string (value)); break; case PROP_ICON_SIZE: hdy_view_switcher_button_set_icon_size (self, g_value_get_int (value)); break; case PROP_NEEDS_ATTENTION: hdy_view_switcher_button_set_needs_attention (self, g_value_get_boolean (value)); break; case PROP_LABEL: hdy_view_switcher_button_set_label (self, g_value_get_string (value)); break; case PROP_ORIENTATION: set_orientation (self, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void hdy_view_switcher_button_finalize (GObject *object) { HdyViewSwitcherButton *self = HDY_VIEW_SWITCHER_BUTTON (object); HdyViewSwitcherButtonPrivate *priv = hdy_view_switcher_button_get_instance_private (self); g_free (priv->icon_name); g_free (priv->label); G_OBJECT_CLASS (hdy_view_switcher_button_parent_class)->finalize (object); } static void hdy_view_switcher_button_class_init (HdyViewSwitcherButtonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->get_property = hdy_view_switcher_button_get_property; object_class->set_property = hdy_view_switcher_button_set_property; object_class->finalize = hdy_view_switcher_button_finalize; g_object_class_override_property (object_class, PROP_LABEL, "label"); g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); /** * HdyViewSwitcherButton:icon-name: * * The icon name representing the view, or %NULL for no icon. * * Since: 0.0.10 */ props[PROP_ICON_NAME] = g_param_spec_string ("icon-name", _("Icon Name"), _("Icon name for image"), "text-x-generic-symbolic", G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE); /** * HdyViewSwitcherButton:icon-size: * * The icon size. * * Since: 0.0.10 */ props[PROP_ICON_SIZE] = g_param_spec_int ("icon-size", _("Icon Size"), _("Symbolic size to use for named icon"), 0, G_MAXINT, GTK_ICON_SIZE_BUTTON, G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE); /** * HdyViewSwitcherButton:needs-attention: * * Sets a flag specifying whether the view requires the user attention. This * is used by the HdyViewSwitcher to change the appearance of the * corresponding button when a view needs attention and it is not the current * one. * * Since: 0.0.10 */ props[PROP_NEEDS_ATTENTION] = g_param_spec_boolean ("needs-attention", _("Needs attention"), _("Hint the view needs attention"), FALSE, G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE); g_object_class_install_properties (object_class, LAST_PROP, props); /* We probably should set the class's CSS name to "hdyviewswitcherbutton" * here, but it doesn't work because GtkCheckButton hardcodes it to "button" * on instanciation, and the functions required to override it are private. * In the meantime, we can use the "hdyviewswitcher > button" CSS selector as * a fairly safe fallback. */ gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/handy/ui/hdy-view-switcher-button.ui"); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, horizontal_box); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, horizontal_image); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, horizontal_label_active); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, horizontal_label_inactive); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, horizontal_label_stack); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, stack); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, vertical_box); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, vertical_image); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, vertical_label_active); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, vertical_label_inactive); gtk_widget_class_bind_template_child_private (widget_class, HdyViewSwitcherButton, vertical_label_stack); gtk_widget_class_bind_template_callback (widget_class, on_active_changed); } static void hdy_view_switcher_button_init (HdyViewSwitcherButton *self) { HdyViewSwitcherButtonPrivate *priv; g_autoptr (GtkCssProvider) provider = gtk_css_provider_new (); priv = hdy_view_switcher_button_get_instance_private (self); priv->icon_size = GTK_ICON_SIZE_BUTTON; gtk_widget_init_template (GTK_WIDGET (self)); gtk_css_provider_load_from_resource (provider, "/sm/puri/handy/style/hdy-view-switcher-button.css"); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (self)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->horizontal_box)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->horizontal_image)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->horizontal_label_active)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->horizontal_label_inactive)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->horizontal_label_stack)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->vertical_box)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->vertical_image)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->vertical_label_active)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->vertical_label_inactive)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (priv->vertical_label_stack)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_stack_set_visible_child (GTK_STACK (priv->stack), GTK_WIDGET (priv->horizontal_box)); gtk_widget_set_focus_on_click (GTK_WIDGET (self), FALSE); /* Make the button look like a regular button and not a radio button. */ gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (self), FALSE); on_active_changed (self); } /** * hdy_view_switcher_button_new: * * Creates a new #HdyViewSwitcherButton widget. * * Returns: a new #HdyViewSwitcherButton * * Since: 0.0.10 */ HdyViewSwitcherButton * hdy_view_switcher_button_new (void) { return g_object_new (HDY_TYPE_VIEW_SWITCHER_BUTTON, NULL); } /** * hdy_view_switcher_button_get_icon_name: * @self: a #HdyViewSwitcherButton * * Gets the icon name representing the view, or %NULL is no icon is set. * * Returns: (transfer none) (nullable): the icon name, or %NULL * * Since: 0.0.10 **/ const gchar * hdy_view_switcher_button_get_icon_name (HdyViewSwitcherButton *self) { HdyViewSwitcherButtonPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self), NULL); priv = hdy_view_switcher_button_get_instance_private (self); return priv->icon_name; } /** * hdy_view_switcher_button_set_icon_name: * @self: a #HdyViewSwitcherButton * @icon_name: (nullable): an icon name or %NULL * * Sets the icon name representing the view, or %NULL to disable the icon. * * Since: 0.0.10 **/ void hdy_view_switcher_button_set_icon_name (HdyViewSwitcherButton *self, const gchar *icon_name) { HdyViewSwitcherButtonPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self)); priv = hdy_view_switcher_button_get_instance_private (self); if (!g_strcmp0 (priv->icon_name, icon_name)) return; g_free (priv->icon_name); priv->icon_name = g_strdup (icon_name); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]); } /** * hdy_view_switcher_button_get_icon_size: * @self: a #HdyViewSwitcherButton * * Gets the icon size used by @self. * * Returns: the icon size used by @self * * Since: 0.0.10 **/ GtkIconSize hdy_view_switcher_button_get_icon_size (HdyViewSwitcherButton *self) { HdyViewSwitcherButtonPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self), GTK_ICON_SIZE_INVALID); priv = hdy_view_switcher_button_get_instance_private (self); return priv->icon_size; } /** * hdy_view_switcher_button_set_icon_size: * @self: a #HdyViewSwitcherButton * @icon_size: the new icon size * * Sets the icon size used by @self. * * Since: 0.0.10 */ void hdy_view_switcher_button_set_icon_size (HdyViewSwitcherButton *self, GtkIconSize icon_size) { HdyViewSwitcherButtonPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self)); priv = hdy_view_switcher_button_get_instance_private (self); if (priv->icon_size == icon_size) return; priv->icon_size = icon_size; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_SIZE]); } /** * hdy_view_switcher_button_get_needs_attention: * @self: a #HdyViewSwitcherButton * * Gets whether the view represented by @self requires the user attention. * * Returns: %TRUE if the view represented by @self requires the user attention, %FALSE otherwise * * Since: 0.0.10 **/ gboolean hdy_view_switcher_button_get_needs_attention (HdyViewSwitcherButton *self) { GtkStyleContext *context; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self), FALSE); context = gtk_widget_get_style_context (GTK_WIDGET (self)); return gtk_style_context_has_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION); } /** * hdy_view_switcher_button_set_needs_attention: * @self: a #HdyViewSwitcherButton * @needs_attention: the new icon size * * Sets whether the view represented by @self requires the user attention. * * Since: 0.0.10 */ void hdy_view_switcher_button_set_needs_attention (HdyViewSwitcherButton *self, gboolean needs_attention) { GtkStyleContext *context; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self)); needs_attention = !!needs_attention; context = gtk_widget_get_style_context (GTK_WIDGET (self)); if (gtk_style_context_has_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION) == needs_attention) return; if (needs_attention) gtk_style_context_add_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION); else gtk_style_context_remove_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NEEDS_ATTENTION]); } /** * hdy_view_switcher_button_get_label: * @self: a #HdyViewSwitcherButton * * Gets the label representing the view. * * Returns: (transfer none) (nullable): the label, or %NULL * * Since: 0.0.10 **/ const gchar * hdy_view_switcher_button_get_label (HdyViewSwitcherButton *self) { HdyViewSwitcherButtonPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self), NULL); priv = hdy_view_switcher_button_get_instance_private (self); return priv->label; } /** * hdy_view_switcher_button_set_label: * @self: a #HdyViewSwitcherButton * @label: (nullable): a label or %NULL * * Sets the label representing the view. * * Since: 0.0.10 **/ void hdy_view_switcher_button_set_label (HdyViewSwitcherButton *self, const gchar *label) { HdyViewSwitcherButtonPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self)); priv = hdy_view_switcher_button_get_instance_private (self); if (!g_strcmp0 (priv->label, label)) return; g_free (priv->label); priv->label = g_strdup (label); g_object_notify (G_OBJECT (self), "label"); } /** * hdy_view_switcher_button_set_narrow_ellipsize: * @self: a #HdyViewSwitcherButton * @mode: a #PangoEllipsizeMode * * Set the mode used to ellipsize the text in narrow mode if there is not * enough space to render the entire string. * * Since: 0.0.10 **/ void hdy_view_switcher_button_set_narrow_ellipsize (HdyViewSwitcherButton *self, PangoEllipsizeMode mode) { HdyViewSwitcherButtonPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER_BUTTON (self)); g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END); priv = hdy_view_switcher_button_get_instance_private (self); gtk_label_set_ellipsize (priv->vertical_label_active, mode); gtk_label_set_ellipsize (priv->vertical_label_inactive, mode); } /** * hdy_view_switcher_button_get_size: * @self: a #HdyViewSwitcherButton * @h_min_width: (out) (nullable): the minimum width when horizontal * @h_nat_width: (out) (nullable): the natural width when horizontal * @v_min_width: (out) (nullable): the minimum width when vertical * @v_nat_width: (out) (nullable): the natural width when vertical * * Measure the size requests in both horizontal and vertical modes. * * Since: 0.0.10 */ void hdy_view_switcher_button_get_size (HdyViewSwitcherButton *self, gint *h_min_width, gint *h_nat_width, gint *v_min_width, gint *v_nat_width) { HdyViewSwitcherButtonPrivate *priv = hdy_view_switcher_button_get_instance_private (self); GtkStyleContext *context; GtkStateFlags state; GtkBorder border; /* gtk_widget_get_preferred_width() doesn't accept both its out parameters to * be NULL, so we must have guards. */ if (h_min_width != NULL || h_nat_width != NULL) gtk_widget_get_preferred_width (GTK_WIDGET (priv->horizontal_box), h_min_width, h_nat_width); if (v_min_width != NULL || v_nat_width != NULL) gtk_widget_get_preferred_width (GTK_WIDGET (priv->vertical_box), v_min_width, v_nat_width); context = gtk_widget_get_style_context (GTK_WIDGET (self)); state = gtk_style_context_get_state (context); gtk_style_context_get_border (context, state, &border); if (h_min_width != NULL) *h_min_width += border.left + border.right; if (h_nat_width != NULL) *h_nat_width += border.left + border.right; if (v_min_width != NULL) *v_min_width += border.left + border.right; if (v_nat_width != NULL) *v_nat_width += border.left + border.right; } libhandy-0.0.13/src/hdy-view-switcher-button.css000066400000000000000000000074571360136463700216070ustar00rootroot00000000000000hdyviewswitcher > button { padding: 0; margin: 0; border-radius: 0; border-top: 0; border-bottom: 0; box-shadow: none; font-size: 1rem; } hdyviewswitcher > button:not(:checked):not(:hover) { background: transparent; } hdyviewswitcher > button:not(:only-child):not(:last-child) { border-right-width: 0px; } hdyviewswitcher > button:not(only-child):first-child:not(:checked):not(:hover) { border-left-color: transparent; } hdyviewswitcher > button:not(:checked):not(:hover) + button:not(:checked):not(:hover) { border-left-color: transparent; } hdyviewswitcher > button:not(only-child):last-child:not(:checked):not(:hover) { border-right-color: transparent; } hdyviewswitcher > button:not(:checked):hover:not(:backdrop) { background-image: image(lighter(@theme_bg_color)); } hdyviewswitcher > button:not(only-child):first-child:not(:checked):hover { border-left-color: shade(@borders, 1.15); } hdyviewswitcher > button:not(:checked):hover + button:not(:checked):not(:hover) { border-left-color: shade(@borders, 1.15); } hdyviewswitcher > button:not(:checked):not(:hover) + button:not(:checked):hover { border-left-color: shade(@borders, 1.15); } hdyviewswitcher > button:not(only-child):last-child:not(:checked):hover { border-right-color: shade(@borders, 1.15); } headerbar hdyviewswitcher > button:not(:checked):hover:not(:backdrop) { /* Reimplementation of $button_fill from Adwaita. The shade should be 4% and * 1% in light and dark mode respectively, so please note the shade is a bit * too strong in dark mode. * * The colors are made only 70% visible to avoid the highlight to be too * strong. */ background-image: linear-gradient(to top, shade(alpha(@theme_bg_color, 0.7), 0.96) 2px, alpha(@theme_bg_color, 0.7)); } headerbar hdyviewswitcher > button:not(only-child):first-child:not(:checked):hover { border-left-color: @borders; } headerbar hdyviewswitcher > button:not(:checked):hover + button:not(:checked):not(:hover) { border-left-color: @borders; } headerbar hdyviewswitcher > button:not(:checked):not(:hover) + button:not(:checked):hover { border-left-color: @borders; } headerbar hdyviewswitcher > button:not(only-child):last-child:not(:checked):hover { border-right-color: @borders; } hdyviewswitcher > button:not(:checked):hover:backdrop, headerbar hdyviewswitcher > button:not(:checked):hover:backdrop { background-image: image(@theme_bg_color); } hdyviewswitcher > button > stack > box.narrow { font-size: 0.75rem; padding-top: 7px; padding-bottom: 5px; } hdyviewswitcher > button > stack > box.narrow image, hdyviewswitcher > button > stack > box.narrow label { padding-left: 8px; padding-right: 8px; } hdyviewswitcher > button box.wide { padding: 8px 12px; } hdyviewswitcher > button > stack > box.wide label:dir(ltr) { padding-right: 7px; } hdyviewswitcher > button > stack > box.wide label:dir(rtl) { padding-left: 7px; } hdyviewswitcher > button > stack > box label.active { font-weight: bold; } hdyviewswitcher > button.needs-attention:active > stack > box label, hdyviewswitcher > button.needs-attention:checked > stack > box label { animation: none; background-image: none; } hdyviewswitcher > button.needs-attention > stack > box label { animation: needs_attention 150ms ease-in; background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(#3584e4), to(transparent)), -gtk-gradient(radial, center center, 0, center center, 0.5, to(rgba(255, 255, 255, 0.769231)), to(transparent)); background-size: 6px 6px, 6px 6px; background-repeat: no-repeat; background-position: right 0px, right 1px; } hdyviewswitcher > button.needs-attention > stack > box label:backdrop { background-size: 6px 6px, 0 0; } hdyviewswitcher > button.needs-attention > stack > box label:dir(rtl) { background-position: left 0px, left 1px; } libhandy-0.0.13/src/hdy-view-switcher-button.ui000066400000000000000000000113541360136463700214230ustar00rootroot00000000000000 libhandy-0.0.13/src/hdy-view-switcher.c000066400000000000000000000625321360136463700177230ustar00rootroot00000000000000/* * Copyright (C) 2019 Zander Brown * Copyright (C) 2019 Purism SPC * * Based on gtkstackswitcher.c, Copyright (c) 2013 Red Hat, Inc. * https://gitlab.gnome.org/GNOME/gtk/blob/a0129f556b1fd655215165739d0277d7f7a2c1a8/gtk/gtkstackswitcher.c * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "hdy-enums.h" #include "hdy-style-private.h" #include "hdy-view-switcher.h" #include "hdy-view-switcher-button-private.h" /** * SECTION:hdy-view-switcher * @short_description: An adaptive view switcher. * @title: HdyViewSwitcher * * An adaptive view switcher, designed to switch between multiple views in a * similar fashion than a #GtkStackSwitcher. * * Depending on the available width, the view switcher can adapt from a wide * mode showing the view's icon and title side by side, to a narrow mode showing * the view's icon and title one on top of the other, in a more compact way. * This can be controlled via the policy property. * * To look good in a header bar, an #HdyViewSwitcher requires to fill its full * height. Contrary to #GtkHeaderBar, #HdyHeaderBar doesn't force a vertical * alignment on its title widget, so we recommend it over #GtkHeaderBar. * * Since: 0.0.10 */ /** * HdyViewSwitcherPolicy: * @HDY_VIEW_SWITCHER_POLICY_AUTO: Automatically adapt to the best fitting mode * @HDY_VIEW_SWITCHER_POLICY_NARROW: Force the narrow mode * @HDY_VIEW_SWITCHER_POLICY_WIDE: Force the wide mode */ #define MIN_NAT_BUTTON_WIDTH 100 #define TIMEOUT_EXPAND 500 enum { PROP_0, PROP_POLICY, PROP_ICON_SIZE, PROP_NARROW_ELLIPSIZE, PROP_STACK, LAST_PROP, }; typedef struct { GHashTable *buttons; gboolean in_child_changed; GtkWidget *switch_button; guint switch_timer; HdyViewSwitcherPolicy policy; GtkIconSize icon_size; PangoEllipsizeMode narrow_ellipsize; GtkStack *stack; } HdyViewSwitcherPrivate; static GParamSpec *props[LAST_PROP]; G_DEFINE_TYPE_WITH_PRIVATE (HdyViewSwitcher, hdy_view_switcher, GTK_TYPE_BOX) static void set_visible_stack_child_for_button (HdyViewSwitcher *self, HdyViewSwitcherButton *button) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); if (priv->in_child_changed) return; gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (g_object_get_data (G_OBJECT (button), "stack-child"))); } static void update_button (HdyViewSwitcher *self, GtkWidget *widget, HdyViewSwitcherButton *button) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); g_autofree gchar *title = NULL; g_autofree gchar *icon_name = NULL; gboolean needs_attention; gtk_container_child_get (GTK_CONTAINER (priv->stack), widget, "title", &title, "icon-name", &icon_name, "needs-attention", &needs_attention, NULL); g_object_set (G_OBJECT (button), "icon-name", icon_name, "icon-size", priv->icon_size, "label", title, "needs-attention", needs_attention, NULL); gtk_widget_set_visible (GTK_WIDGET (button), gtk_widget_get_visible (widget) && (title != NULL || icon_name != NULL)); } static void on_stack_child_updated (GtkWidget *widget, GParamSpec *pspec, HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); update_button (self, widget, g_hash_table_lookup (priv->buttons, widget)); } static void on_position_updated (GtkWidget *widget, GParamSpec *pspec, HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); GtkWidget *button = g_hash_table_lookup (priv->buttons, widget); gint position; gtk_container_child_get (GTK_CONTAINER (priv->stack), widget, "position", &position, NULL); gtk_box_reorder_child (GTK_BOX (self), button, position); } static void remove_switch_timer (HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); if (!priv->switch_timer) return; g_source_remove (priv->switch_timer); priv->switch_timer = 0; } static gboolean hdy_view_switcher_switch_timeout (gpointer data) { HdyViewSwitcher *self = HDY_VIEW_SWITCHER (data); HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); GtkWidget *button = priv->switch_button; priv->switch_timer = 0; priv->switch_button = NULL; if (button) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); return G_SOURCE_REMOVE; } static gboolean hdy_view_switcher_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time) { HdyViewSwitcher *self = HDY_VIEW_SWITCHER (widget); HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); GtkAllocation allocation; GtkWidget *button; GHashTableIter iter; gpointer value; gboolean retval = FALSE; gtk_widget_get_allocation (widget, &allocation); x += allocation.x; y += allocation.y; button = NULL; g_hash_table_iter_init (&iter, priv->buttons); while (g_hash_table_iter_next (&iter, NULL, &value)) { gtk_widget_get_allocation (GTK_WIDGET (value), &allocation); if (x >= allocation.x && x <= allocation.x + allocation.width && y >= allocation.y && y <= allocation.y + allocation.height) { button = GTK_WIDGET (value); retval = TRUE; break; } } if (button != priv->switch_button) remove_switch_timer (self); priv->switch_button = button; if (button && !priv->switch_timer) { priv->switch_timer = gdk_threads_add_timeout (TIMEOUT_EXPAND, hdy_view_switcher_switch_timeout, self); g_source_set_name_by_id (priv->switch_timer, "[gtk+] hdy_view_switcher_switch_timeout"); } return retval; } static void hdy_view_switcher_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time) { HdyViewSwitcher *self = HDY_VIEW_SWITCHER (widget); remove_switch_timer (self); } static void add_button_for_stack_child (HdyViewSwitcher *self, GtkWidget *stack_child) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (self)); HdyViewSwitcherButton *button = hdy_view_switcher_button_new (); g_object_set_data (G_OBJECT (button), "stack-child", stack_child); g_object_bind_property (self, "icon-size", button, "icon-size", G_BINDING_SYNC_CREATE); hdy_view_switcher_button_set_narrow_ellipsize (button, priv->narrow_ellipsize); update_button (self, stack_child, button); if (children != NULL) gtk_radio_button_join_group (GTK_RADIO_BUTTON (button), GTK_RADIO_BUTTON (children->data)); gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (button)); g_signal_connect_swapped (button, "clicked", G_CALLBACK (set_visible_stack_child_for_button), self); g_signal_connect (stack_child, "notify::visible", G_CALLBACK (on_stack_child_updated), self); g_signal_connect (stack_child, "child-notify::title", G_CALLBACK (on_stack_child_updated), self); g_signal_connect (stack_child, "child-notify::icon-name", G_CALLBACK (on_stack_child_updated), self); g_signal_connect (stack_child, "child-notify::needs-attention", G_CALLBACK (on_stack_child_updated), self); g_signal_connect (stack_child, "child-notify::position", G_CALLBACK (on_position_updated), self); g_hash_table_insert (priv->buttons, stack_child, button); } static void add_button_for_stack_child_cb (GtkWidget *stack_child, HdyViewSwitcher *self) { g_return_if_fail (HDY_IS_VIEW_SWITCHER (self)); g_return_if_fail (GTK_IS_WIDGET (stack_child)); add_button_for_stack_child (self, stack_child); } static void remove_button_for_stack_child (HdyViewSwitcher *self, GtkWidget *stack_child) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); g_signal_handlers_disconnect_by_func (stack_child, on_stack_child_updated, self); g_signal_handlers_disconnect_by_func (stack_child, on_position_updated, self); gtk_container_remove (GTK_CONTAINER (self), g_hash_table_lookup (priv->buttons, stack_child)); g_hash_table_remove (priv->buttons, stack_child); } static void remove_button_for_stack_child_cb (GtkWidget *stack_child, HdyViewSwitcher *self) { g_return_if_fail (HDY_IS_VIEW_SWITCHER (self)); g_return_if_fail (GTK_IS_WIDGET (stack_child)); remove_button_for_stack_child (self, stack_child); } static void update_active_button_for_visible_stack_child (HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); GtkWidget *visible_stack_child = gtk_stack_get_visible_child (priv->stack); GtkWidget *button = g_hash_table_lookup (priv->buttons, visible_stack_child); if (button == NULL) return; priv->in_child_changed = TRUE; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); priv->in_child_changed = FALSE; } static void disconnect_stack_signals (HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); g_signal_handlers_disconnect_by_func (priv->stack, add_button_for_stack_child, self); g_signal_handlers_disconnect_by_func (priv->stack, remove_button_for_stack_child, self); g_signal_handlers_disconnect_by_func (priv->stack, update_active_button_for_visible_stack_child, self); g_signal_handlers_disconnect_by_func (priv->stack, disconnect_stack_signals, self); } static void connect_stack_signals (HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); g_signal_connect_object (priv->stack, "add", G_CALLBACK (add_button_for_stack_child), self, G_CONNECT_AFTER | G_CONNECT_SWAPPED); g_signal_connect_object (priv->stack, "remove", G_CALLBACK (remove_button_for_stack_child), self, G_CONNECT_AFTER | G_CONNECT_SWAPPED); g_signal_connect_object (priv->stack, "notify::visible-child", G_CALLBACK (update_active_button_for_visible_stack_child), self, G_CONNECT_SWAPPED); g_signal_connect_object (priv->stack, "destroy", G_CALLBACK (disconnect_stack_signals), self, G_CONNECT_SWAPPED); } static void hdy_view_switcher_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { HdyViewSwitcher *self = HDY_VIEW_SWITCHER (object); switch (prop_id) { case PROP_POLICY: g_value_set_enum (value, hdy_view_switcher_get_policy (self)); break; case PROP_ICON_SIZE: g_value_set_int (value, hdy_view_switcher_get_icon_size (self)); break; case PROP_NARROW_ELLIPSIZE: g_value_set_enum (value, hdy_view_switcher_get_narrow_ellipsize (self)); break; case PROP_STACK: g_value_set_object (value, hdy_view_switcher_get_stack (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void hdy_view_switcher_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { HdyViewSwitcher *self = HDY_VIEW_SWITCHER (object); switch (prop_id) { case PROP_POLICY: hdy_view_switcher_set_policy (self, g_value_get_enum (value)); break; case PROP_ICON_SIZE: hdy_view_switcher_set_icon_size (self, g_value_get_int (value)); break; case PROP_NARROW_ELLIPSIZE: hdy_view_switcher_set_narrow_ellipsize (self, g_value_get_enum (value)); break; case PROP_STACK: hdy_view_switcher_set_stack (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void hdy_view_switcher_dispose (GObject *object) { HdyViewSwitcher *self = HDY_VIEW_SWITCHER (object); remove_switch_timer (self); hdy_view_switcher_set_stack (self, NULL); G_OBJECT_CLASS (hdy_view_switcher_parent_class)->dispose (object); } static void hdy_view_switcher_finalize (GObject *object) { HdyViewSwitcher *self = HDY_VIEW_SWITCHER (object); HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); g_hash_table_destroy (priv->buttons); G_OBJECT_CLASS (hdy_view_switcher_parent_class)->finalize (object); } static void hdy_view_switcher_get_preferred_width (GtkWidget *widget, gint *min, gint *nat) { HdyViewSwitcher *self = HDY_VIEW_SWITCHER (widget); HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (self)); gint max_h_min = 0, max_h_nat = 0, max_v_min = 0, max_v_nat = 0; gint n_children = 0; for (GList *l = children; l != NULL; l = g_list_next (l)) { gint h_min = 0, h_nat = 0, v_min = 0, v_nat = 0; if (!gtk_widget_get_visible (l->data)) continue; hdy_view_switcher_button_get_size (HDY_VIEW_SWITCHER_BUTTON (l->data), &h_min, &h_nat, &v_min, &v_nat); max_h_min = MAX (h_min, max_h_min); max_h_nat = MAX (h_nat, max_h_nat); max_v_min = MAX (v_min, max_v_min); max_v_nat = MAX (v_nat, max_v_nat); n_children++; } /* Make the buttons ask at least a minimum arbitrary size for their natural * width. This prevents them from looking terribly narrow in a very wide bar. */ max_h_nat = MAX (max_h_nat, MIN_NAT_BUTTON_WIDTH); max_v_nat = MAX (max_v_nat, MIN_NAT_BUTTON_WIDTH); switch (priv->policy) { case HDY_VIEW_SWITCHER_POLICY_NARROW: *min = max_v_min * n_children; *nat = max_v_nat * n_children; break; case HDY_VIEW_SWITCHER_POLICY_WIDE: *min = max_h_min * n_children; *nat = max_h_nat * n_children; break; case HDY_VIEW_SWITCHER_POLICY_AUTO: default: *min = max_v_min * n_children; *nat = max_h_nat * n_children; break; } } static gint is_narrow (HdyViewSwitcher *self, gint width) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (self)); gint max_h_min = 0; gint n_children = 0; if (priv->policy == HDY_VIEW_SWITCHER_POLICY_NARROW) return TRUE; if (priv->policy == HDY_VIEW_SWITCHER_POLICY_WIDE) return FALSE; for (GList *l = children; l != NULL; l = g_list_next (l)) { gint h_min = 0; hdy_view_switcher_button_get_size (HDY_VIEW_SWITCHER_BUTTON (l->data), &h_min, NULL, NULL, NULL); max_h_min = MAX (max_h_min, h_min); n_children++; } return (max_h_min * n_children) > width; } static void hdy_view_switcher_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (widget)); GtkOrientation orientation = is_narrow (HDY_VIEW_SWITCHER (widget), allocation->width) ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL; for (GList *l = children; l != NULL; l = g_list_next (l)) gtk_orientable_set_orientation (GTK_ORIENTABLE (l->data), orientation); GTK_WIDGET_CLASS (hdy_view_switcher_parent_class)->size_allocate (widget, allocation); } static void hdy_view_switcher_class_init (HdyViewSwitcherClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->get_property = hdy_view_switcher_get_property; object_class->set_property = hdy_view_switcher_set_property; object_class->dispose = hdy_view_switcher_dispose; object_class->finalize = hdy_view_switcher_finalize; widget_class->size_allocate = hdy_view_switcher_size_allocate; widget_class->get_preferred_width = hdy_view_switcher_get_preferred_width; widget_class->drag_motion = hdy_view_switcher_drag_motion; widget_class->drag_leave = hdy_view_switcher_drag_leave; /** * HdyViewSwitcher:policy: * * The #HdyViewSwitcherPolicy the view switcher should use to determine which * mode to use. * * Since: 0.0.10 */ props[PROP_POLICY] = g_param_spec_enum ("policy", _("Policy"), _("The policy to determine the mode to use"), HDY_TYPE_VIEW_SWITCHER_POLICY, HDY_VIEW_SWITCHER_POLICY_AUTO, G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * HdyViewSwitcher:icon-size: * * Use the "icon-size" property to hint the icons to use, you almost certainly * want to leave this as %GTK_ICON_SIZE_BUTTON. * * Since: 0.0.10 */ props[PROP_ICON_SIZE] = g_param_spec_int ("icon-size", _("Icon Size"), _("Symbolic size to use for named icon"), 0, G_MAXINT, GTK_ICON_SIZE_BUTTON, G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * HdyViewSwitcher:narrow-ellipsize: * * The preferred place to ellipsize the string, if the narrow mode label does * not have enough room to display the entire string, specified as a * #PangoEllipsizeMode. * * Note that setting this property to a value other than %PANGO_ELLIPSIZE_NONE * has the side-effect that the label requests only enough space to display * the ellipsis. * * Since: 0.0.10 */ props[PROP_NARROW_ELLIPSIZE] = g_param_spec_enum ("narrow-ellipsize", _("Narrow ellipsize"), _("The preferred place to ellipsize the string, if the narrow mode label does not have enough room to display the entire string"), PANGO_TYPE_ELLIPSIZE_MODE, PANGO_ELLIPSIZE_NONE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * HdyViewSwitcher:stack: * * The #GtkStack the view switcher controls. * * Since: 0.0.10 */ props[PROP_STACK] = g_param_spec_object ("stack", _("Stack"), _("Stack"), GTK_TYPE_STACK, G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_css_name (widget_class, "hdyviewswitcher"); } static void hdy_view_switcher_init (HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv = hdy_view_switcher_get_instance_private (self); g_autoptr (GtkCssProvider) provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (provider, "/sm/puri/handy/style/hdy-view-switcher.css"); gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (self)), GTK_STYLE_PROVIDER (provider), HDY_STYLE_PROVIDER_PRIORITY); gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); priv->icon_size = GTK_ICON_SIZE_BUTTON; priv->buttons = g_hash_table_new (g_direct_hash, g_direct_equal); gtk_widget_set_valign (GTK_WIDGET (self), GTK_ALIGN_FILL); gtk_box_set_homogeneous (GTK_BOX (self), TRUE); gtk_drag_dest_set (GTK_WIDGET (self), 0, NULL, 0, 0); gtk_drag_dest_set_track_motion (GTK_WIDGET (self), TRUE); } /** * hdy_view_switcher_new: * * Creates a new #HdyViewSwitcher widget. * * Returns: a new #HdyViewSwitcher * * Since: 0.0.10 */ HdyViewSwitcher * hdy_view_switcher_new (void) { return g_object_new (HDY_TYPE_VIEW_SWITCHER, NULL); } /** * hdy_view_switcher_get_policy: * @self: a #HdyViewSwitcher * * Gets the policy of @self. * * Returns: the policy of @self * * Since: 0.0.10 */ HdyViewSwitcherPolicy hdy_view_switcher_get_policy (HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER (self), HDY_VIEW_SWITCHER_POLICY_AUTO); priv = hdy_view_switcher_get_instance_private (self); return priv->policy; } /** * hdy_view_switcher_set_policy: * @self: a #HdyViewSwitcher * @policy: the new policy * * Sets the policy of @self. * * Since: 0.0.10 */ void hdy_view_switcher_set_policy (HdyViewSwitcher *self, HdyViewSwitcherPolicy policy) { HdyViewSwitcherPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER (self)); priv = hdy_view_switcher_get_instance_private (self); if (priv->policy == policy) return; priv->policy = policy; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POLICY]); gtk_widget_queue_resize (GTK_WIDGET (self)); } /** * hdy_view_switcher_get_icon_size: * @self: a #HdyViewSwitcher * * Get the icon size of the images used in the #HdyViewSwitcher. * * See: hdy_view_switcher_set_icon_size() * * Returns: the icon size of the images * * Since: 0.0.10 */ GtkIconSize hdy_view_switcher_get_icon_size (HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER (self), GTK_ICON_SIZE_BUTTON); priv = hdy_view_switcher_get_instance_private (self); return priv->icon_size; } /** * hdy_view_switcher_set_icon_size: * @self: a #HdyViewSwitcher * @icon_size: the new icon size * * Change the icon size hint for the icons in a #HdyViewSwitcher. * * Since: 0.0.10 */ void hdy_view_switcher_set_icon_size (HdyViewSwitcher *self, GtkIconSize icon_size) { HdyViewSwitcherPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER (self)); priv = hdy_view_switcher_get_instance_private (self); if (priv->icon_size == icon_size) return; priv->icon_size = icon_size; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_SIZE]); } /** * hdy_view_switcher_get_narrow_ellipsize: * @self: a #HdyViewSwitcher * * Get the ellipsizing position of the narrow mode label. See * hdy_view_switcher_set_narrow_ellipsize(). * * Returns: #PangoEllipsizeMode * * Since: 0.0.10 **/ PangoEllipsizeMode hdy_view_switcher_get_narrow_ellipsize (HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER (self), PANGO_ELLIPSIZE_NONE); priv = hdy_view_switcher_get_instance_private (self); return priv->narrow_ellipsize; } /** * hdy_view_switcher_set_narrow_ellipsize: * @self: a #HdyViewSwitcher * @mode: a #PangoEllipsizeMode * * Set the mode used to ellipsize the text in narrow mode if there is not * enough space to render the entire string. * * Since: 0.0.10 **/ void hdy_view_switcher_set_narrow_ellipsize (HdyViewSwitcher *self, PangoEllipsizeMode mode) { HdyViewSwitcherPrivate *priv; GHashTableIter iter; gpointer button; g_return_if_fail (HDY_IS_VIEW_SWITCHER (self)); g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END); priv = hdy_view_switcher_get_instance_private (self); if ((PangoEllipsizeMode) priv->narrow_ellipsize == mode) return; priv->narrow_ellipsize = mode; g_hash_table_iter_init (&iter, priv->buttons); while (g_hash_table_iter_next (&iter, NULL, &button)) hdy_view_switcher_button_set_narrow_ellipsize (HDY_VIEW_SWITCHER_BUTTON (button), mode); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NARROW_ELLIPSIZE]); } /** * hdy_view_switcher_get_stack: * @self: a #HdyViewSwitcher * * Get the #GtkStack being controlled by the #HdyViewSwitcher. * * See: hdy_view_switcher_set_stack() * * Returns: (nullable) (transfer none): the #GtkStack, or %NULL if none has been set * * Since: 0.0.10 */ GtkStack * hdy_view_switcher_get_stack (HdyViewSwitcher *self) { HdyViewSwitcherPrivate *priv; g_return_val_if_fail (HDY_IS_VIEW_SWITCHER (self), NULL); priv = hdy_view_switcher_get_instance_private (self); return priv->stack; } /** * hdy_view_switcher_set_stack: * @self: a #HdyViewSwitcher * @stack: (nullable): a #GtkStack * * Sets the #GtkStack to control. * * Since: 0.0.10 */ void hdy_view_switcher_set_stack (HdyViewSwitcher *self, GtkStack *stack) { HdyViewSwitcherPrivate *priv; g_return_if_fail (HDY_IS_VIEW_SWITCHER (self)); g_return_if_fail (stack == NULL || GTK_IS_STACK (stack)); priv = hdy_view_switcher_get_instance_private (self); if (priv->stack == stack) return; if (priv->stack) { disconnect_stack_signals (self); gtk_container_foreach (GTK_CONTAINER (priv->stack), (GtkCallback) remove_button_for_stack_child_cb, self); } g_set_object (&priv->stack, stack); if (priv->stack) { gtk_container_foreach (GTK_CONTAINER (priv->stack), (GtkCallback) add_button_for_stack_child_cb, self); update_active_button_for_visible_stack_child (self); connect_stack_signals (self); } gtk_widget_queue_resize (GTK_WIDGET (self)); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STACK]); } libhandy-0.0.13/src/hdy-view-switcher.css000066400000000000000000000000571360136463700202630ustar00rootroot00000000000000hdyviewswitcher { margin: 0; padding: 0; } libhandy-0.0.13/src/hdy-view-switcher.h000066400000000000000000000031261360136463700177220ustar00rootroot00000000000000/* * Copyright (C) 2019 Zander Brown * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define HDY_TYPE_VIEW_SWITCHER (hdy_view_switcher_get_type()) struct _HdyViewSwitcherClass { GtkBoxClass parent_class; }; G_DECLARE_DERIVABLE_TYPE (HdyViewSwitcher, hdy_view_switcher, HDY, VIEW_SWITCHER, GtkBox) typedef enum { HDY_VIEW_SWITCHER_POLICY_AUTO, HDY_VIEW_SWITCHER_POLICY_NARROW, HDY_VIEW_SWITCHER_POLICY_WIDE, } HdyViewSwitcherPolicy; HdyViewSwitcher *hdy_view_switcher_new (void); HdyViewSwitcherPolicy hdy_view_switcher_get_policy (HdyViewSwitcher *self); void hdy_view_switcher_set_policy (HdyViewSwitcher *self, HdyViewSwitcherPolicy policy); GtkIconSize hdy_view_switcher_get_icon_size (HdyViewSwitcher *self); void hdy_view_switcher_set_icon_size (HdyViewSwitcher *self, GtkIconSize icon_size); PangoEllipsizeMode hdy_view_switcher_get_narrow_ellipsize (HdyViewSwitcher *self); void hdy_view_switcher_set_narrow_ellipsize (HdyViewSwitcher *self, PangoEllipsizeMode mode); GtkStack *hdy_view_switcher_get_stack (HdyViewSwitcher *self); void hdy_view_switcher_set_stack (HdyViewSwitcher *self, GtkStack *stack); G_END_DECLS libhandy-0.0.13/src/meson.build000066400000000000000000000160641360136463700163360ustar00rootroot00000000000000libhandy_header_subdir = package_subdir / package_api_name libhandy_header_dir = get_option('includedir') / libhandy_header_subdir libhandy_resources = gnome.compile_resources( 'hdy-resources', 'handy.gresources.xml', c_name: 'hdy', ) hdy_enum_headers = ['hdy-arrows.h', 'hdy-fold.h', 'hdy-header-bar.h', 'hdy-leaflet.h', 'hdy-paginator.h', 'hdy-squeezer.h', 'hdy-view-switcher.h', ] version_data = configuration_data() version_data.set('HDY_MAJOR_VERSION', handy_version_major) version_data.set('HDY_MINOR_VERSION', handy_version_minor) version_data.set('HDY_MICRO_VERSION', handy_version_micro) version_data.set('HDY_VERSION', meson.project_version()) # TODO: Once Meson 0.50.0 is required, use this to prevent installation: # install: installable, if installable hdy_version_h = configure_file( input: 'hdy-version.h.in', output: 'hdy-version.h', install_dir: libhandy_header_dir, configuration: version_data) else hdy_version_h = configure_file( input: 'hdy-version.h.in', output: 'hdy-version.h', configuration: version_data) endif libhandy_generated_headers = [ ] if installable install_headers(['handy.h'], subdir: libhandy_header_subdir) endif # Filled out in the subdirs libhandy_public_headers = [] libhandy_public_sources = [] libhandy_private_sources = [] hdy_enums = gnome.mkenums('hdy-enums', h_template: 'hdy-enums.h.in', c_template: 'hdy-enums.c.in', sources: hdy_enum_headers, install_header: installable, install_dir: libhandy_header_dir, ) libhandy_public_sources += [hdy_enums[0]] libhandy_generated_headers += [hdy_enums[1]] src_headers = [ 'hdy-action-row.h', 'hdy-animation.h', 'hdy-arrows.h', 'hdy-column.h', 'hdy-combo-row.h', 'hdy-deprecation-macros.h', 'hdy-dialer-button.h', 'hdy-dialer-cycle-button.h', 'hdy-dialer.h', 'hdy-dialog.h', 'hdy-enum-value-object.h', 'hdy-expander-row.h', 'hdy-fold.h', 'hdy-header-bar.h', 'hdy-header-group.h', 'hdy-keypad.h', 'hdy-leaflet.h', 'hdy-list-box.h', 'hdy-main.h', 'hdy-paginator.h', 'hdy-preferences-group.h', 'hdy-preferences-page.h', 'hdy-preferences-row.h', 'hdy-preferences-window.h', 'hdy-search-bar.h', 'hdy-squeezer.h', 'hdy-string-utf8.h', 'hdy-swipe-group.h', 'hdy-swipeable.h', 'hdy-title-bar.h', 'hdy-value-object.h', 'hdy-view-switcher.h', 'hdy-view-switcher-bar.h', ] sed = find_program('sed', required: true) gen_public_types = find_program('gen-public-types.sh', required: true) libhandy_init_public_types = custom_target('hdy-public-types.c', output: 'hdy-public-types.c', input: [src_headers, libhandy_generated_headers], command: [gen_public_types, '@INPUT@'], capture: true, ) src_sources = [ 'gtkprogresstracker.c', 'gtk-window.c', 'hdy-action-row.c', 'hdy-animation.c', 'hdy-arrows.c', 'hdy-column.c', 'hdy-combo-row.c', 'hdy-dialer-button.c', 'hdy-dialer-cycle-button.c', 'hdy-dialer.c', 'hdy-dialog.c', 'hdy-enum-value-object.c', 'hdy-expander-row.c', 'hdy-fold.c', 'hdy-header-bar.c', 'hdy-header-group.c', 'hdy-keypad-button.c', 'hdy-keypad.c', 'hdy-leaflet.c', 'hdy-list-box.c', 'hdy-main.c', 'hdy-paginator.c', 'hdy-paginator-box.c', 'hdy-preferences-group.c', 'hdy-preferences-page.c', 'hdy-preferences-row.c', 'hdy-preferences-window.c', 'hdy-search-bar.c', 'hdy-shadow-helper.c', 'hdy-squeezer.c', 'hdy-string-utf8.c', 'hdy-swipe-group.c', 'hdy-swipe-tracker.c', 'hdy-swipeable.c', 'hdy-title-bar.c', 'hdy-value-object.c', 'hdy-view-switcher.c', 'hdy-view-switcher-bar.c', 'hdy-view-switcher-button.c', ] libhandy_public_headers += files(src_headers) libhandy_public_sources += files(src_sources) if installable install_headers(src_headers, subdir: libhandy_header_subdir) endif libhandy_sources = [ libhandy_generated_headers, libhandy_public_sources, libhandy_private_sources, libhandy_resources, libhandy_init_public_types, ] glib_min_version = '>= 2.44' libhandy_deps = [ dependency('glib-2.0', version: glib_min_version), dependency('gio-2.0', version: glib_min_version), dependency('gmodule-2.0', version: glib_min_version), dependency('gtk+-3.0', version: '>= 3.24.1'), cc.find_library('m', required: false), cc.find_library('rt', required: false), ] libhandy_c_args = [ '-DG_LOG_DOMAIN="Handy"', ] libhandy_link_args = [] libhandy_symbols_file = 'libhandy.syms' # Check linker flags ld_version_script_arg = '-Wl,--version-script,@0@/@1@'.format(meson.source_root(), libhandy_symbols_file) if cc.links('int main() { return 0; }', args : ld_version_script_arg, name : 'ld_supports_version_script') libhandy_link_args += [ld_version_script_arg] endif static = get_option('static') if static libtype = 'static_library' else libtype = 'shared_library' endif # set default libdir on win32 for libhandy target to keep MinGW compatibility if target_system == 'windows' handy_libdir = [true] else handy_libdir = libdir endif libhandy = build_target( 'handy-' + apiversion, libhandy_sources, soversion: soversion, c_args: libhandy_c_args, dependencies: libhandy_deps, include_directories: [ root_inc, src_inc ], install: installable, link_args: libhandy_link_args, install_dir: handy_libdir, target_type: libtype, ) libhandy_dep = declare_dependency( sources: libhandy_generated_headers, dependencies: libhandy_deps, link_with: libhandy, include_directories: include_directories('.'), ) if introspection libhandy_gir_extra_args = [ '--c-include=handy.h', '--quiet', '-DHANDY_COMPILATION', ] libhandy_gir = gnome.generate_gir(libhandy, sources: libhandy_generated_headers + libhandy_public_headers + libhandy_public_sources, nsversion: apiversion, namespace: 'Handy', export_packages: package_api_name, symbol_prefix: 'hdy', identifier_prefix: 'Hdy', link_with: libhandy, includes: ['Gio-2.0', 'Gtk-3.0'], install: true, install_dir_gir: girdir, install_dir_typelib: typelibdir, extra_args: libhandy_gir_extra_args, ) if get_option('vapi') libhandy_vapi = gnome.generate_vapi(package_api_name, sources: libhandy_gir[0], packages: [ 'gio-2.0', 'gtk+-3.0' ], install: true, install_dir: vapidir, metadata_dirs: [ meson.current_source_dir() ], ) endif endif pkgg = import('pkgconfig') if installable pkgg.generate( libraries: [libhandy], subdirs: libhandy_header_subdir, version: meson.project_version(), name: 'Handy', filebase: package_api_name, description: 'Handy Mobile widgets', requires: 'gtk+-3.0', install_dir: libdir / 'pkgconfig', ) endif libhandy-0.0.13/tests/000077500000000000000000000000001360136463700145405ustar00rootroot00000000000000libhandy-0.0.13/tests/meson.build000066400000000000000000000024421360136463700167040ustar00rootroot00000000000000if get_option('tests') test_env = [ 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), 'G_DEBUG=gc-friendly,fatal-warnings', 'GSETTINGS_BACKEND=memory', 'PYTHONDONTWRITEBYTECODE=yes', 'MALLOC_CHECK_=2', ] test_cflags = [ '-DHDY_LOG_DOMAIN="Handy"', '-DTEST_DATA_DIR="@0@/data"'.format(meson.current_source_dir()), ] test_link_args = [ '-fPIC', ] test_names = [ 'test-action-row', 'test-arrows', 'test-combo-row', 'test-dialer', 'test-dialer-cycle-button', 'test-dialog', 'test-expander-row', 'test-header-bar', 'test-header-group', 'test-keypad', 'test-paginator', 'test-preferences-group', 'test-preferences-page', 'test-preferences-row', 'test-preferences-window', 'test-search-bar', 'test-squeezer', 'test-string-utf8', 'test-swipe-group', 'test-value-object', 'test-view-switcher', 'test-view-switcher-bar', ] foreach test_name : test_names t = executable(test_name, [test_name + '.c'] + libhandy_generated_headers, c_args: test_cflags, link_args: test_link_args, dependencies: libhandy_deps + [libhandy_dep], pie: true, ) test(test_name, t, env: test_env) endforeach endif libhandy-0.0.13/tests/test-action-row.c000066400000000000000000000064151360136463700177510ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_action_row_add (void) { g_autoptr (HdyActionRow) row = NULL; GtkWidget *sw; row = g_object_ref_sink (HDY_ACTION_ROW (hdy_action_row_new ())); g_assert_nonnull (row); sw = gtk_switch_new (); g_assert_nonnull (sw); gtk_container_add (GTK_CONTAINER (row), sw); } static void test_hdy_action_row_add_action (void) { g_autoptr (HdyActionRow) row = NULL; GtkWidget *sw; row = g_object_ref_sink (HDY_ACTION_ROW (hdy_action_row_new ())); g_assert_nonnull (row); sw = gtk_switch_new (); g_assert_nonnull (sw); hdy_action_row_add_action (row, sw); } static void test_hdy_action_row_add_prefix (void) { g_autoptr (HdyActionRow) row = NULL; GtkWidget *radio; row = g_object_ref_sink (HDY_ACTION_ROW (hdy_action_row_new ())); g_assert_nonnull (row); radio = gtk_radio_button_new (NULL); g_assert_nonnull (radio); hdy_action_row_add_prefix (row, radio); } static void test_hdy_action_row_title (void) { g_autoptr (HdyActionRow) row = NULL; row = g_object_ref_sink (HDY_ACTION_ROW (hdy_action_row_new ())); g_assert_nonnull (row); g_assert_cmpstr (hdy_action_row_get_title (row), ==, ""); hdy_action_row_set_title (row, "Dummy title"); g_assert_cmpstr (hdy_action_row_get_title (row), ==, "Dummy title"); } static void test_hdy_action_row_subtitle (void) { g_autoptr (HdyActionRow) row = NULL; row = g_object_ref_sink (HDY_ACTION_ROW (hdy_action_row_new ())); g_assert_nonnull (row); g_assert_cmpstr (hdy_action_row_get_subtitle (row), ==, ""); hdy_action_row_set_subtitle (row, "Dummy subtitle"); g_assert_cmpstr (hdy_action_row_get_subtitle (row), ==, "Dummy subtitle"); } static void test_hdy_action_row_icon_name (void) { g_autoptr (HdyActionRow) row = NULL; row = g_object_ref_sink (HDY_ACTION_ROW (hdy_action_row_new ())); g_assert_nonnull (row); g_assert_null (hdy_action_row_get_icon_name (row)); hdy_action_row_set_icon_name (row, "dummy-icon-name"); g_assert_cmpstr (hdy_action_row_get_icon_name (row), ==, "dummy-icon-name"); } static void test_hdy_action_row_use_undeline (void) { g_autoptr (HdyActionRow) row = NULL; row = g_object_ref_sink (HDY_ACTION_ROW (hdy_action_row_new ())); g_assert_nonnull (row); g_assert_false (hdy_action_row_get_use_underline (row)); hdy_action_row_set_use_underline (row, TRUE); g_assert_true (hdy_action_row_get_use_underline (row)); hdy_action_row_set_use_underline (row, FALSE); g_assert_false (hdy_action_row_get_use_underline (row)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/ActionRow/add", test_hdy_action_row_add); g_test_add_func("/Handy/ActionRow/add_action", test_hdy_action_row_add_action); g_test_add_func("/Handy/ActionRow/add_prefix", test_hdy_action_row_add_prefix); g_test_add_func("/Handy/ActionRow/title", test_hdy_action_row_title); g_test_add_func("/Handy/ActionRow/subtitle", test_hdy_action_row_subtitle); g_test_add_func("/Handy/ActionRow/icon_name", test_hdy_action_row_icon_name); g_test_add_func("/Handy/ActionRow/use_underline", test_hdy_action_row_use_undeline); return g_test_run(); } libhandy-0.0.13/tests/test-arrows.c000066400000000000000000000030241360136463700171750ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_arrows_setters (void) { HdyArrows *arrows; arrows = HDY_ARROWS (hdy_arrows_new()); /* Check the getters and setters */ hdy_arrows_set_duration (arrows, 10); g_assert_cmpint (hdy_arrows_get_duration (arrows), ==, 10); hdy_arrows_set_direction (arrows, HDY_ARROWS_DIRECTION_LEFT); g_assert_cmpint (hdy_arrows_get_direction (arrows), ==, HDY_ARROWS_DIRECTION_LEFT); hdy_arrows_set_count(arrows, 5); g_assert_cmpint (hdy_arrows_get_count (arrows), ==, 5); hdy_arrows_animate (arrows); gtk_widget_destroy (GTK_WIDGET (arrows)); } static void test_hdy_arrows_gobject (void) { HdyArrows *arrows; arrows = g_object_new (HDY_TYPE_ARROWS, "duration", 10, "direction", HDY_ARROWS_DIRECTION_LEFT, "count", 5, NULL); g_assert_cmpint (hdy_arrows_get_duration (arrows), ==, 10); g_assert_cmpint (hdy_arrows_get_direction (arrows), ==, HDY_ARROWS_DIRECTION_LEFT); g_assert_cmpint (hdy_arrows_get_count (arrows), ==, 5); hdy_arrows_animate (arrows); gtk_widget_destroy (GTK_WIDGET(arrows)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/Arrows/methods", test_hdy_arrows_setters); g_test_add_func("/Handy/Arrows/gobject", test_hdy_arrows_gobject); return g_test_run(); } libhandy-0.0.13/tests/test-combo-row.c000066400000000000000000000033411360136463700175660ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_combo_row_set_for_enum (void) { g_autoptr (HdyComboRow) row = NULL; GListModel *model; HdyEnumValueObject *value; row = g_object_ref_sink (HDY_COMBO_ROW (hdy_combo_row_new ())); g_assert_nonnull (row); g_assert_null (hdy_combo_row_get_model (row)); hdy_combo_row_set_for_enum (row, HDY_TYPE_FOLD, hdy_enum_value_row_name, NULL, NULL); model = hdy_combo_row_get_model (row); g_assert_true (G_IS_LIST_MODEL (model)); g_assert_cmpuint (g_list_model_get_n_items (model), ==, 2); value = g_list_model_get_item (model, 0); g_assert_true (HDY_IS_ENUM_VALUE_OBJECT (value)); g_assert_cmpstr (hdy_enum_value_object_get_nick (value), ==, "unfolded"); value = g_list_model_get_item (model, 1); g_assert_true (HDY_IS_ENUM_VALUE_OBJECT (value)); g_assert_cmpstr (hdy_enum_value_object_get_nick (value), ==, "folded"); } static void test_hdy_combo_row_use_subtitle (void) { g_autoptr (HdyComboRow) row = NULL; row = g_object_ref_sink (HDY_COMBO_ROW (hdy_combo_row_new ())); g_assert_nonnull (row); g_assert_false (hdy_combo_row_get_use_subtitle (row)); hdy_combo_row_set_use_subtitle (row, TRUE); g_assert_true (hdy_combo_row_get_use_subtitle (row)); hdy_combo_row_set_use_subtitle (row, FALSE); g_assert_false (hdy_combo_row_get_use_subtitle (row)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/ComboRow/set_for_enum", test_hdy_combo_row_set_for_enum); g_test_add_func("/Handy/ComboRow/use_subtitle", test_hdy_combo_row_use_subtitle); return g_test_run(); } libhandy-0.0.13/tests/test-dialer-cycle-button.c000066400000000000000000000023651360136463700215350ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include gint notified; static void cycle_end_cb(GtkWidget *widget, gpointer data) { notified++; } static void test_hdy_dialer_cycle_button_cycle_end(void) { GtkWidget *btn; btn = hdy_dialer_cycle_button_new ("abc"); g_signal_connect (btn, "cycle-end", G_CALLBACK (cycle_end_cb), NULL); hdy_dialer_cycle_button_stop_cycle (HDY_DIALER_CYCLE_BUTTON (btn)); g_assert_cmpint (1, ==, notified); notified = 0; } static void test_hdy_dialer_cycle_button_cycle_timeout(void) { HdyDialerCycleButton *btn; btn = HDY_DIALER_CYCLE_BUTTON (hdy_dialer_cycle_button_new ("abc")); g_assert_cmpint (1000, ==, hdy_dialer_cycle_button_get_cycle_timeout (btn)); hdy_dialer_cycle_button_set_cycle_timeout (btn, 10); g_assert_cmpint (10, ==, hdy_dialer_cycle_button_get_cycle_timeout (btn)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/DialerCycleButton/cycle_end", test_hdy_dialer_cycle_button_cycle_end); g_test_add_func("/Handy/DialerCycleButton/cycle_timeout", test_hdy_dialer_cycle_button_cycle_timeout); return g_test_run(); } libhandy-0.0.13/tests/test-dialer.c000066400000000000000000000101241360136463700171170ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include gint notified; static void notify_cb (GtkWidget *widget, gpointer data) { notified++; } static void test_hdy_dialer_setnumber (void) { GtkWidget *dialer; notified = 0; dialer = hdy_dialer_new (); g_signal_connect (dialer, "notify::number", G_CALLBACK (notify_cb), NULL); g_assert_cmpstr (hdy_dialer_get_number (HDY_DIALER (dialer)), ==, ""); hdy_dialer_set_number (HDY_DIALER (dialer), "#1234"); g_assert_cmpstr (hdy_dialer_get_number (HDY_DIALER (dialer)), ==, "#1234"); g_assert_cmpint (notified, ==, 1); /* Check that we're assigning to the string and not overwriting */ hdy_dialer_set_number (HDY_DIALER (dialer), "#123"); g_assert_cmpstr (hdy_dialer_get_number (HDY_DIALER (dialer)), !=, "#1234"); g_assert_cmpint (notified, ==, 2); /* Do the same using the GObject property */ g_object_set (G_OBJECT (dialer), "number", "#12", NULL); g_assert_cmpstr (hdy_dialer_get_number (HDY_DIALER (dialer)), !=, "#123"); g_assert_cmpstr (hdy_dialer_get_number (HDY_DIALER (dialer)), ==, "#12"); g_assert_cmpint (notified, ==, 3); } static void test_hdy_dialer_clear_number (void) { GtkWidget *dialer; notified = 0; dialer = hdy_dialer_new (); g_signal_connect (dialer, "notify::number", G_CALLBACK (notify_cb), NULL); g_assert_cmpstr (hdy_dialer_get_number (HDY_DIALER (dialer)), ==, ""); hdy_dialer_clear_number (HDY_DIALER (dialer)); g_assert_cmpint (notified, ==, 0); hdy_dialer_set_number (HDY_DIALER (dialer), "#1234"); g_assert_cmpstr (hdy_dialer_get_number (HDY_DIALER (dialer)), ==, "#1234"); g_assert_cmpint (notified, ==, 1); hdy_dialer_clear_number (HDY_DIALER (dialer)); g_assert_cmpint (notified, ==, 2); hdy_dialer_clear_number (HDY_DIALER (dialer)); g_assert_cmpint (notified, ==, 2); } static void test_hdy_dialer_action_buttons (void) { HdyDialer *dialer = HDY_DIALER (hdy_dialer_new ()); gboolean val; notified = 0; g_signal_connect (dialer, "notify::show-action-buttons", G_CALLBACK (notify_cb), NULL); /* Getters/setters */ g_assert_true (hdy_dialer_get_show_action_buttons (dialer)); hdy_dialer_set_show_action_buttons (dialer, FALSE); g_assert_false (hdy_dialer_get_show_action_buttons (dialer)); hdy_dialer_set_show_action_buttons (dialer, TRUE); g_assert_true (hdy_dialer_get_show_action_buttons (dialer)); g_assert_cmpint (notified, ==, 2); /* Property */ g_object_set (dialer, "show-action-buttons", FALSE, NULL); g_object_get (dialer, "show-action-buttons", &val, NULL); g_assert_false (val); g_assert_cmpint (notified, ==, 3); /* Setting the same value should not notify */ hdy_dialer_set_show_action_buttons (dialer, FALSE); g_assert_cmpint (notified, ==, 3); } static void test_hdy_dialer_relief (void) { HdyDialer *dialer = HDY_DIALER (hdy_dialer_new ()); GtkReliefStyle style; notified = 0; g_signal_connect (dialer, "notify::relief", G_CALLBACK (notify_cb), NULL); g_assert_cmpint (hdy_dialer_get_relief (dialer), ==, GTK_RELIEF_NORMAL); hdy_dialer_set_relief (dialer, GTK_RELIEF_NONE); g_assert_cmpint (hdy_dialer_get_relief (dialer), ==, GTK_RELIEF_NONE); hdy_dialer_set_relief (dialer, GTK_RELIEF_NORMAL); g_assert_cmpint (hdy_dialer_get_relief (dialer), ==, GTK_RELIEF_NORMAL); g_assert_cmpint (notified, ==, 2); /* Property */ g_object_set (dialer, "relief", GTK_RELIEF_NONE, NULL); g_object_get (dialer, "relief", &style, NULL); g_assert_cmpint (style, ==, GTK_RELIEF_NONE); /* Setting the same value should not notify */ hdy_dialer_set_relief (dialer, GTK_RELIEF_NONE); g_assert_cmpint (notified, ==, 3); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func ("/Handy/Dialer/setnumber", test_hdy_dialer_setnumber); g_test_add_func ("/Handy/Dialer/clear_number", test_hdy_dialer_clear_number); g_test_add_func ("/Handy/Dialer/action_buttons", test_hdy_dialer_action_buttons); g_test_add_func ("/Handy/Dialer/relief", test_hdy_dialer_relief); return g_test_run (); } libhandy-0.0.13/tests/test-dialog.c000066400000000000000000000041131360136463700171170ustar00rootroot00000000000000/* * Copyright © 2018 Zander Brown * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include gint win_width = 0; gint win_height = 0; gint dlg_width = 0; gint dlg_height = 0; static void win_size_cb (GtkWidget *widget, GdkRectangle *allocation, gpointer user_data) { gtk_window_get_size (GTK_WINDOW (widget), &win_width, &win_height); } static void dlg_size_cb (GtkWidget *widget, GdkRectangle *allocation, gpointer user_data) { gtk_window_get_size (GTK_WINDOW (widget), &dlg_width, &dlg_height); } static void test_hdy_dialog_is_small (void) { GtkWidget *window; GtkWidget *dialog; win_width = 0; win_height = 0; dlg_width = 0; dlg_height = 0; window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_resize (GTK_WINDOW (window), 400, 400); g_signal_connect (window, "size-allocate", G_CALLBACK (win_size_cb), NULL); gtk_widget_show (window); dialog = hdy_dialog_new (GTK_WINDOW (window)); g_signal_connect (dialog, "size-allocate", G_CALLBACK (dlg_size_cb), NULL); gtk_widget_show (dialog); g_assert_cmpint (win_height, ==, dlg_height); g_assert_cmpint (win_width, ==, dlg_width); } static void test_hdy_dialog_normal (void) { GtkWidget *window; GtkWidget *dialog; win_width = 0; win_height = 0; dlg_width = 0; dlg_height = 0; window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_resize (GTK_WINDOW (window), 800, 800); g_signal_connect (window, "size-allocate", G_CALLBACK (win_size_cb), NULL); gtk_widget_show (window); dialog = hdy_dialog_new (GTK_WINDOW (window)); g_signal_connect (dialog, "size-allocate", G_CALLBACK (dlg_size_cb), NULL); gtk_widget_show (dialog); g_assert_cmpint (win_height, !=, dlg_height); g_assert_cmpint (win_width, !=, dlg_width); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); g_test_add_func("/Handy/Dialog/is_small", test_hdy_dialog_is_small); g_test_add_func("/Handy/Dialog/normal", test_hdy_dialog_normal); return g_test_run(); } libhandy-0.0.13/tests/test-expander-row.c000066400000000000000000000045411360136463700203000ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_expander_row_expanded (void) { g_autoptr (HdyExpanderRow) row = NULL; row = g_object_ref_sink (HDY_EXPANDER_ROW (hdy_expander_row_new ())); g_assert_nonnull (row); g_assert_false (hdy_expander_row_get_expanded (row)); hdy_expander_row_set_expanded (row, TRUE); g_assert_true (hdy_expander_row_get_expanded (row)); hdy_expander_row_set_expanded (row, FALSE); g_assert_false (hdy_expander_row_get_expanded (row)); } static void test_hdy_expander_row_enable_expansion (void) { g_autoptr (HdyExpanderRow) row = NULL; row = g_object_ref_sink (HDY_EXPANDER_ROW (hdy_expander_row_new ())); g_assert_nonnull (row); g_assert_true (hdy_expander_row_get_enable_expansion (row)); g_assert_false (hdy_expander_row_get_expanded (row)); hdy_expander_row_set_expanded (row, TRUE); g_assert_true (hdy_expander_row_get_expanded (row)); hdy_expander_row_set_enable_expansion (row, FALSE); g_assert_false (hdy_expander_row_get_enable_expansion (row)); g_assert_false (hdy_expander_row_get_expanded (row)); hdy_expander_row_set_expanded (row, TRUE); g_assert_false (hdy_expander_row_get_expanded (row)); hdy_expander_row_set_enable_expansion (row, TRUE); g_assert_true (hdy_expander_row_get_enable_expansion (row)); g_assert_true (hdy_expander_row_get_expanded (row)); } static void test_hdy_expander_row_show_enable_switch (void) { g_autoptr (HdyExpanderRow) row = NULL; row = g_object_ref_sink (HDY_EXPANDER_ROW (hdy_expander_row_new ())); g_assert_nonnull (row); g_assert_false (hdy_expander_row_get_show_enable_switch (row)); hdy_expander_row_set_show_enable_switch (row, TRUE); g_assert_true (hdy_expander_row_get_show_enable_switch (row)); hdy_expander_row_set_show_enable_switch (row, FALSE); g_assert_false (hdy_expander_row_get_show_enable_switch (row)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/ExpanderRow/expanded", test_hdy_expander_row_expanded); g_test_add_func("/Handy/ExpanderRow/enable_expansion", test_hdy_expander_row_enable_expansion); g_test_add_func("/Handy/ExpanderRow/show_enable_switch", test_hdy_expander_row_show_enable_switch); return g_test_run(); } libhandy-0.0.13/tests/test-header-bar.c000066400000000000000000000137611360136463700176630ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_header_bar_pack (void) { g_autoptr (HdyHeaderBar) bar = NULL; GtkWidget *widget; bar = g_object_ref_sink (HDY_HEADER_BAR (hdy_header_bar_new ())); g_assert_nonnull (bar); widget = gtk_switch_new (); g_assert_nonnull (widget); hdy_header_bar_pack_start (bar, widget); widget = gtk_switch_new (); g_assert_nonnull (widget); hdy_header_bar_pack_end (bar, widget); } static void test_hdy_header_bar_title (void) { g_autoptr (HdyHeaderBar) bar = NULL; bar = g_object_ref_sink (HDY_HEADER_BAR (hdy_header_bar_new ())); g_assert_nonnull (bar); g_assert_null (hdy_header_bar_get_title (bar)); hdy_header_bar_set_title (bar, "Dummy title"); g_assert_cmpstr (hdy_header_bar_get_title (bar), ==, "Dummy title"); hdy_header_bar_set_title (bar, NULL); g_assert_null (hdy_header_bar_get_title (bar)); } static void test_hdy_header_bar_subtitle (void) { g_autoptr (HdyHeaderBar) bar = NULL; bar = g_object_ref_sink (HDY_HEADER_BAR (hdy_header_bar_new ())); g_assert_nonnull (bar); g_assert_null (hdy_header_bar_get_subtitle (bar)); hdy_header_bar_set_subtitle (bar, "Dummy subtitle"); g_assert_cmpstr (hdy_header_bar_get_subtitle (bar), ==, "Dummy subtitle"); hdy_header_bar_set_subtitle (bar, NULL); g_assert_null (hdy_header_bar_get_subtitle (bar)); } static void test_hdy_header_bar_custom_title (void) { g_autoptr (HdyHeaderBar) bar = NULL; GtkWidget *widget; bar = g_object_ref_sink (HDY_HEADER_BAR (hdy_header_bar_new ())); g_assert_nonnull (bar); g_assert_null (hdy_header_bar_get_custom_title (bar)); widget = gtk_switch_new (); g_assert_nonnull (widget); hdy_header_bar_set_custom_title (bar, widget); g_assert (hdy_header_bar_get_custom_title (bar) == widget); hdy_header_bar_set_custom_title (bar, NULL); g_assert_null (hdy_header_bar_get_custom_title (bar)); } static void test_hdy_header_bar_show_close_button (void) { g_autoptr (HdyHeaderBar) bar = NULL; bar = g_object_ref_sink (HDY_HEADER_BAR (hdy_header_bar_new ())); g_assert_nonnull (bar); g_assert_false (hdy_header_bar_get_show_close_button (bar)); hdy_header_bar_set_show_close_button (bar, TRUE); g_assert_true (hdy_header_bar_get_show_close_button (bar)); hdy_header_bar_set_show_close_button (bar, FALSE); g_assert_false (hdy_header_bar_get_show_close_button (bar)); } static void test_hdy_header_bar_has_subtitle (void) { g_autoptr (HdyHeaderBar) bar = NULL; bar = g_object_ref_sink (HDY_HEADER_BAR (hdy_header_bar_new ())); g_assert_nonnull (bar); g_assert_true (hdy_header_bar_get_has_subtitle (bar)); hdy_header_bar_set_has_subtitle (bar, FALSE); g_assert_false (hdy_header_bar_get_has_subtitle (bar)); hdy_header_bar_set_has_subtitle (bar, TRUE); g_assert_true (hdy_header_bar_get_has_subtitle (bar)); } static void test_hdy_header_bar_decoration_layout (void) { g_autoptr (HdyHeaderBar) bar = NULL; bar = g_object_ref_sink (HDY_HEADER_BAR (hdy_header_bar_new ())); g_assert_nonnull (bar); g_assert_null (hdy_header_bar_get_decoration_layout (bar)); hdy_header_bar_set_decoration_layout (bar, ":"); g_assert_cmpstr (hdy_header_bar_get_decoration_layout (bar), ==, ":"); hdy_header_bar_set_decoration_layout (bar, NULL); g_assert_null (hdy_header_bar_get_decoration_layout (bar)); } static void test_hdy_header_bar_centering_policy (void) { g_autoptr (HdyHeaderBar) bar = NULL; bar = g_object_ref_sink (HDY_HEADER_BAR (hdy_header_bar_new ())); g_assert_nonnull (bar); g_assert_cmpint (hdy_header_bar_get_centering_policy (bar), ==, HDY_CENTERING_POLICY_LOOSE); hdy_header_bar_set_centering_policy (bar, HDY_CENTERING_POLICY_STRICT); g_assert_cmpint (hdy_header_bar_get_centering_policy (bar), ==, HDY_CENTERING_POLICY_STRICT); hdy_header_bar_set_centering_policy (bar, HDY_CENTERING_POLICY_LOOSE); g_assert_cmpint (hdy_header_bar_get_centering_policy (bar), ==, HDY_CENTERING_POLICY_LOOSE); } static void test_hdy_header_bar_transition_duration (void) { g_autoptr (HdyHeaderBar) bar = NULL; bar = g_object_ref_sink (HDY_HEADER_BAR (hdy_header_bar_new ())); g_assert_nonnull (bar); g_assert_cmpuint (hdy_header_bar_get_transition_duration (bar), ==, 200); hdy_header_bar_set_transition_duration (bar, 0); g_assert_cmpuint (hdy_header_bar_get_transition_duration (bar), ==, 0); hdy_header_bar_set_transition_duration (bar, 1000); g_assert_cmpuint (hdy_header_bar_get_transition_duration (bar), ==, 1000); } static void test_hdy_header_bar_interpolate_size (void) { g_autoptr (HdyHeaderBar) bar = NULL; bar = g_object_ref_sink (HDY_HEADER_BAR (hdy_header_bar_new ())); g_assert_nonnull (bar); g_assert_false (hdy_header_bar_get_interpolate_size (bar)); hdy_header_bar_set_interpolate_size (bar, TRUE); g_assert_true (hdy_header_bar_get_interpolate_size (bar)); hdy_header_bar_set_interpolate_size (bar, FALSE); g_assert_false (hdy_header_bar_get_interpolate_size (bar)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/HeaderBar/pack", test_hdy_header_bar_pack); g_test_add_func("/Handy/HeaderBar/title", test_hdy_header_bar_title); g_test_add_func("/Handy/HeaderBar/subtitle", test_hdy_header_bar_subtitle); g_test_add_func("/Handy/HeaderBar/custom_title", test_hdy_header_bar_custom_title); g_test_add_func("/Handy/HeaderBar/show_close_button", test_hdy_header_bar_show_close_button); g_test_add_func("/Handy/HeaderBar/has_subtitle", test_hdy_header_bar_has_subtitle); g_test_add_func("/Handy/HeaderBar/decoration_layout", test_hdy_header_bar_decoration_layout); g_test_add_func("/Handy/HeaderBar/centering_policy", test_hdy_header_bar_centering_policy); g_test_add_func("/Handy/HeaderBar/transition_duration", test_hdy_header_bar_transition_duration); g_test_add_func("/Handy/HeaderBar/interpolate_size", test_hdy_header_bar_interpolate_size); return g_test_run(); } libhandy-0.0.13/tests/test-header-group.c000066400000000000000000000041311360136463700202420ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_header_group_focus (void) { HdyHeaderGroup *hg; GtkHeaderBar *bar1, *bar2 = NULL; hg = HDY_HEADER_GROUP (hdy_header_group_new ()); bar1 = hdy_header_group_get_focus (hg); g_assert_null (bar1); g_object_get (hg, "focus", &bar2, NULL); g_assert (bar1 == bar2); bar1 = GTK_HEADER_BAR (gtk_header_bar_new ()); hdy_header_group_add_header_bar (hg, GTK_HEADER_BAR (bar1)); hdy_header_group_set_focus (hg, GTK_HEADER_BAR (bar1)); bar2 = hdy_header_group_get_focus (hg); g_assert (bar1 == bar2); g_object_get (hg, "focus", &bar2, NULL); g_assert (bar1 == bar2); g_object_unref (hg); } static void test_hdy_header_group_add_remove (void) { HdyHeaderGroup *hg; GtkHeaderBar *bar1, *bar2; hg = HDY_HEADER_GROUP (hdy_header_group_new ()); bar1 = GTK_HEADER_BAR (gtk_header_bar_new ()); bar2 = GTK_HEADER_BAR (gtk_header_bar_new ()); g_assert_cmpint (g_slist_length (hdy_header_group_get_header_bars (hg)), ==, 0); hdy_header_group_add_header_bar (hg, GTK_HEADER_BAR (bar1)); g_assert_cmpint (g_slist_length (hdy_header_group_get_header_bars (hg)), ==, 1); hdy_header_group_add_header_bar (hg, GTK_HEADER_BAR (bar2)); g_assert_cmpint (g_slist_length (hdy_header_group_get_header_bars (hg)), ==, 2); hdy_header_group_set_focus (hg, GTK_HEADER_BAR (bar2)); hdy_header_group_remove_header_bar (hg, GTK_HEADER_BAR (bar2)); g_assert_cmpint (g_slist_length (hdy_header_group_get_header_bars (hg)), ==, 1); g_assert_null (hdy_header_group_get_focus (hg)); hdy_header_group_remove_header_bar (hg, GTK_HEADER_BAR (bar1)); g_assert_cmpint (g_slist_length (hdy_header_group_get_header_bars (hg)), ==, 0); g_object_unref (hg); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/HeaderGroup/focus", test_hdy_header_group_focus); g_test_add_func("/Handy/HeaderGroup/add_remove", test_hdy_header_group_add_remove); return g_test_run(); } libhandy-0.0.13/tests/test-keypad.c000066400000000000000000000051211360136463700171350ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include #include gint notified; static void notify_cb (GtkWidget *widget, gpointer data) { notified++; } static void test_hdy_keypad_show_symbols (void) { HdyKeypad *keypad = HDY_KEYPAD (hdy_keypad_new (TRUE, FALSE)); GList *l; GList *list; list = gtk_container_get_children (GTK_CONTAINER (keypad)); for (l = list; l != NULL; l = l->next) { if (HDY_IS_KEYPAD_BUTTON(l->data)) { gboolean value; g_object_get (l->data, "show-symbols", &value, NULL); g_assert_false (value); } } g_list_free (list); } static void test_hdy_keypad_set_actions (void) { HdyKeypad *keypad = HDY_KEYPAD (hdy_keypad_new (FALSE, TRUE)); GtkWidget *button_right = gtk_button_new (); GtkWidget *button_left = gtk_button_new (); // Right extra button g_assert (gtk_grid_get_child_at (GTK_GRID (keypad), 2, 3) != NULL); // Left extra button g_assert (gtk_grid_get_child_at (GTK_GRID (keypad), 0, 3) != NULL); hdy_keypad_set_right_action (keypad, button_right); hdy_keypad_set_left_action (keypad, button_left); g_assert (button_right == gtk_grid_get_child_at (GTK_GRID (keypad), 2, 3)); g_assert (button_left == gtk_grid_get_child_at (GTK_GRID (keypad), 0, 3)); hdy_keypad_set_right_action (keypad, NULL); g_assert (gtk_grid_get_child_at (GTK_GRID (keypad), 2, 3) == NULL); hdy_keypad_set_left_action (keypad, NULL); g_assert (gtk_grid_get_child_at (GTK_GRID (keypad), 0, 3) == NULL); } static void test_hdy_keypad_button_clicked (void) { HdyKeypad *keypad = HDY_KEYPAD (hdy_keypad_new (FALSE, TRUE)); GtkWidget *entry = gtk_entry_new (); GList *l; GList *list; notified = 0; hdy_keypad_set_entry (keypad, GTK_ENTRY (entry)); g_signal_connect (hdy_keypad_get_entry (keypad), "insert-text", G_CALLBACK (notify_cb), NULL); list = gtk_container_get_children (GTK_CONTAINER (keypad)); for (l = list; l != NULL; l = l->next) { if (HDY_IS_KEYPAD_BUTTON(l->data)) { GtkButton *btn = GTK_BUTTON (l->data); gtk_button_clicked (btn); } } g_assert_cmpint (notified, ==, 10); g_list_free (list); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func ("/Handy/Keypad/show_symbols", test_hdy_keypad_show_symbols); g_test_add_func ("/Handy/Keypad/set_actions", test_hdy_keypad_set_actions); g_test_add_func ("/Handy/Keypad/button_click", test_hdy_keypad_button_clicked); return g_test_run (); } libhandy-0.0.13/tests/test-paginator.c000066400000000000000000000221471360136463700176530ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include gint notified; static void notify_cb (GtkWidget *widget, gpointer data) { notified++; } static void test_hdy_paginator_add_remove (void) { HdyPaginator *paginator; GtkWidget *child1, *child2, *child3; paginator = HDY_PAGINATOR (hdy_paginator_new ()); child1 = gtk_label_new (""); child2 = gtk_label_new (""); child3 = gtk_label_new (""); notified = 0; g_signal_connect (paginator, "notify::n-pages", G_CALLBACK (notify_cb), NULL); g_assert_cmpuint (hdy_paginator_get_n_pages (paginator), ==, 0); gtk_container_add (GTK_CONTAINER (paginator), child1); g_assert_cmpuint (hdy_paginator_get_n_pages (paginator), ==, 1); g_assert_cmpint (notified, ==, 1); hdy_paginator_prepend (paginator, child2); g_assert_cmpuint (hdy_paginator_get_n_pages (paginator), ==, 2); g_assert_cmpint (notified, ==, 2); hdy_paginator_insert (paginator, child3, 1); g_assert_cmpuint (hdy_paginator_get_n_pages (paginator), ==, 3); g_assert_cmpint (notified, ==, 3); hdy_paginator_reorder (paginator, child3, 0); g_assert_cmpuint (hdy_paginator_get_n_pages (paginator), ==, 3); g_assert_cmpint (notified, ==, 3); gtk_container_remove (GTK_CONTAINER (paginator), child2); g_assert_cmpuint (hdy_paginator_get_n_pages (paginator), ==, 2); g_assert_cmpint (notified, ==, 4); gtk_container_remove (GTK_CONTAINER (paginator), child1); g_assert_cmpuint (hdy_paginator_get_n_pages (paginator), ==, 1); g_assert_cmpint (notified, ==, 5); gtk_container_remove (GTK_CONTAINER (paginator), child3); g_assert_cmpuint (hdy_paginator_get_n_pages (paginator), ==, 0); g_assert_cmpint (notified, ==, 6); g_object_unref (paginator); } static void test_hdy_paginator_scroll_to (void) { HdyPaginator *paginator; GtkWidget *child1, *child2, *child3; paginator = HDY_PAGINATOR (hdy_paginator_new ()); child1 = gtk_label_new (""); child2 = gtk_label_new (""); child3 = gtk_label_new (""); notified = 0; g_signal_connect (paginator, "notify::position", G_CALLBACK (notify_cb), NULL); gtk_container_add (GTK_CONTAINER (paginator), child1); gtk_container_add (GTK_CONTAINER (paginator), child2); gtk_container_add (GTK_CONTAINER (paginator), child3); /* Since tests are done synchronously, avoid animations */ hdy_paginator_set_animation_duration (paginator, 0); g_assert_cmpfloat(hdy_paginator_get_position (paginator), ==, 0); g_assert_cmpint (notified, ==, 0); hdy_paginator_scroll_to (paginator, child3); g_assert_cmpfloat(hdy_paginator_get_position (paginator), ==, 2); g_assert_cmpint (notified, ==, 1); hdy_paginator_scroll_to (paginator, child2); g_assert_cmpfloat(hdy_paginator_get_position (paginator), ==, 1); g_assert_cmpint (notified, ==, 2); g_object_unref (paginator); } static void test_hdy_paginator_interactive (void) { HdyPaginator *paginator = HDY_PAGINATOR (hdy_paginator_new ()); gboolean interactive; notified = 0; g_signal_connect (paginator, "notify::interactive", G_CALLBACK (notify_cb), NULL); /* Accessors */ g_assert_true (hdy_paginator_get_interactive (paginator)); hdy_paginator_set_interactive (paginator, FALSE); g_assert_false (hdy_paginator_get_interactive (paginator)); g_assert_cmpint (notified, ==, 1); /* Property */ g_object_set (paginator, "interactive", TRUE, NULL); g_object_get (paginator, "interactive", &interactive, NULL); g_assert_true (interactive); g_assert_cmpint (notified, ==, 2); /* Setting the same value should not notify */ hdy_paginator_set_interactive (paginator, TRUE); g_assert_cmpint (notified, ==, 2); } static void test_hdy_paginator_indicator_style (void) { HdyPaginator *paginator = HDY_PAGINATOR (hdy_paginator_new ()); HdyPaginatorIndicatorStyle indicator_style; notified = 0; g_signal_connect (paginator, "notify::indicator-style", G_CALLBACK (notify_cb), NULL); /* Accessors */ g_assert_cmpint (hdy_paginator_get_indicator_style (paginator), ==, HDY_PAGINATOR_INDICATOR_STYLE_NONE); hdy_paginator_set_indicator_style (paginator, HDY_PAGINATOR_INDICATOR_STYLE_DOTS); g_assert_cmpint (hdy_paginator_get_indicator_style (paginator), ==, HDY_PAGINATOR_INDICATOR_STYLE_DOTS); g_assert_cmpint (notified, ==, 1); hdy_paginator_set_indicator_style (paginator, HDY_PAGINATOR_INDICATOR_STYLE_LINES); g_assert_cmpint (hdy_paginator_get_indicator_style (paginator), ==, HDY_PAGINATOR_INDICATOR_STYLE_LINES); g_assert_cmpint (notified, ==, 2); /* Property */ g_object_set (paginator, "indicator-style", HDY_PAGINATOR_INDICATOR_STYLE_DOTS, NULL); g_object_get (paginator, "indicator-style", &indicator_style, NULL); g_assert_cmpint (indicator_style, ==, HDY_PAGINATOR_INDICATOR_STYLE_DOTS); g_assert_cmpint (notified, ==, 3); /* Setting the same value should not notify */ hdy_paginator_set_indicator_style (paginator, HDY_PAGINATOR_INDICATOR_STYLE_DOTS); g_assert_cmpint (notified, ==, 3); } static void test_hdy_paginator_indicator_spacing (void) { HdyPaginator *paginator = HDY_PAGINATOR (hdy_paginator_new ()); uint spacing; notified = 0; g_signal_connect (paginator, "notify::indicator-spacing", G_CALLBACK (notify_cb), NULL); /* Accessors */ g_assert_cmpuint (hdy_paginator_get_indicator_spacing (paginator), ==, 0); hdy_paginator_set_indicator_spacing (paginator, 12); g_assert_cmpuint (hdy_paginator_get_indicator_spacing (paginator), ==, 12); g_assert_cmpint (notified, ==, 1); /* Property */ g_object_set (paginator, "indicator-spacing", 6, NULL); g_object_get (paginator, "indicator-spacing", &spacing, NULL); g_assert_cmpuint (spacing, ==, 6); g_assert_cmpint (notified, ==, 2); /* Setting the same value should not notify */ hdy_paginator_set_indicator_spacing (paginator, 6); g_assert_cmpint (notified, ==, 2); } static void test_hdy_paginator_center_content (void) { HdyPaginator *paginator = HDY_PAGINATOR (hdy_paginator_new ()); gboolean center_content; notified = 0; g_signal_connect (paginator, "notify::center-content", G_CALLBACK (notify_cb), NULL); /* Accessors */ g_assert_false (hdy_paginator_get_center_content (paginator)); hdy_paginator_set_center_content (paginator, TRUE); g_assert_true (hdy_paginator_get_center_content (paginator)); g_assert_cmpint (notified, ==, 1); /* Property */ g_object_set (paginator, "center-content", FALSE, NULL); g_object_get (paginator, "center-content", ¢er_content, NULL); g_assert_false (center_content); g_assert_cmpint (notified, ==, 2); /* Setting the same value should not notify */ hdy_paginator_set_center_content (paginator, FALSE); g_assert_cmpint (notified, ==, 2); } static void test_hdy_paginator_spacing (void) { HdyPaginator *paginator = HDY_PAGINATOR (hdy_paginator_new ()); uint spacing; notified = 0; g_signal_connect (paginator, "notify::spacing", G_CALLBACK (notify_cb), NULL); /* Accessors */ g_assert_cmpuint (hdy_paginator_get_spacing (paginator), ==, 0); hdy_paginator_set_spacing (paginator, 12); g_assert_cmpuint (hdy_paginator_get_spacing (paginator), ==, 12); g_assert_cmpint (notified, ==, 1); /* Property */ g_object_set (paginator, "spacing", 6, NULL); g_object_get (paginator, "spacing", &spacing, NULL); g_assert_cmpuint (spacing, ==, 6); g_assert_cmpint (notified, ==, 2); /* Setting the same value should not notify */ hdy_paginator_set_spacing (paginator, 6); g_assert_cmpint (notified, ==, 2); } static void test_hdy_paginator_animation_duration (void) { HdyPaginator *paginator = HDY_PAGINATOR (hdy_paginator_new ()); uint duration; notified = 0; g_signal_connect (paginator, "notify::animation-duration", G_CALLBACK (notify_cb), NULL); /* Accessors */ g_assert_cmpuint (hdy_paginator_get_animation_duration (paginator), ==, 250); hdy_paginator_set_animation_duration (paginator, 200); g_assert_cmpuint (hdy_paginator_get_animation_duration (paginator), ==, 200); g_assert_cmpint (notified, ==, 1); /* Property */ g_object_set (paginator, "animation-duration", 500, NULL); g_object_get (paginator, "animation-duration", &duration, NULL); g_assert_cmpuint (duration, ==, 500); g_assert_cmpint (notified, ==, 2); /* Setting the same value should not notify */ hdy_paginator_set_animation_duration (paginator, 500); g_assert_cmpint (notified, ==, 2); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/Paginator/add_remove", test_hdy_paginator_add_remove); g_test_add_func("/Handy/Paginator/scroll_to", test_hdy_paginator_scroll_to); g_test_add_func("/Handy/Paginator/interactive", test_hdy_paginator_interactive); g_test_add_func("/Handy/Paginator/indicator_style", test_hdy_paginator_indicator_style); g_test_add_func("/Handy/Paginator/indicator_spacing", test_hdy_paginator_indicator_spacing); g_test_add_func("/Handy/Paginator/center_content", test_hdy_paginator_center_content); g_test_add_func("/Handy/Paginator/spacing", test_hdy_paginator_spacing); g_test_add_func("/Handy/Paginator/animation_duration", test_hdy_paginator_animation_duration); return g_test_run(); } libhandy-0.0.13/tests/test-preferences-group.c000066400000000000000000000045061360136463700213210ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_preferences_group_add (void) { g_autoptr (HdyPreferencesGroup) group = NULL; HdyPreferencesRow *row; GtkWidget *widget; group = g_object_ref_sink (HDY_PREFERENCES_GROUP (hdy_preferences_group_new ())); g_assert_nonnull (group); row = hdy_preferences_row_new (); g_assert_nonnull (row); gtk_container_add (GTK_CONTAINER (group), GTK_WIDGET (row)); widget = gtk_switch_new (); g_assert_nonnull (widget); gtk_container_add (GTK_CONTAINER (group), widget); g_assert (G_TYPE_CHECK_INSTANCE_TYPE (gtk_widget_get_parent (GTK_WIDGET (row)), GTK_TYPE_LIST_BOX)); g_assert (G_TYPE_CHECK_INSTANCE_TYPE (gtk_widget_get_parent (widget), GTK_TYPE_BOX)); } static void test_hdy_preferences_group_title (void) { g_autoptr (HdyPreferencesGroup) group = NULL; group = g_object_ref_sink (HDY_PREFERENCES_GROUP (hdy_preferences_group_new ())); g_assert_nonnull (group); g_assert_cmpstr (hdy_preferences_group_get_title (group), ==, ""); hdy_preferences_group_set_title (group, "Dummy title"); g_assert_cmpstr (hdy_preferences_group_get_title (group), ==, "Dummy title"); hdy_preferences_group_set_title (group, NULL); g_assert_cmpstr (hdy_preferences_group_get_title (group), ==, ""); } static void test_hdy_preferences_group_description (void) { g_autoptr (HdyPreferencesGroup) group = NULL; group = g_object_ref_sink (HDY_PREFERENCES_GROUP (hdy_preferences_group_new ())); g_assert_nonnull (group); g_assert_cmpstr (hdy_preferences_group_get_description (group), ==, ""); hdy_preferences_group_set_description (group, "Dummy description"); g_assert_cmpstr (hdy_preferences_group_get_description (group), ==, "Dummy description"); hdy_preferences_group_set_description (group, NULL); g_assert_cmpstr (hdy_preferences_group_get_description (group), ==, ""); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/PreferencesGroup/add", test_hdy_preferences_group_add); g_test_add_func("/Handy/PreferencesGroup/title", test_hdy_preferences_group_title); g_test_add_func("/Handy/PreferencesGroup/description", test_hdy_preferences_group_description); return g_test_run(); } libhandy-0.0.13/tests/test-preferences-page.c000066400000000000000000000043101360136463700210720ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_preferences_page_add (void) { g_autoptr (HdyPreferencesPage) page = NULL; HdyPreferencesGroup *group; GtkWidget *widget; page = g_object_ref_sink (HDY_PREFERENCES_PAGE (hdy_preferences_page_new ())); g_assert_nonnull (page); group = hdy_preferences_group_new (); g_assert_nonnull (group); gtk_container_add (GTK_CONTAINER (page), GTK_WIDGET (group)); widget = gtk_switch_new (); g_assert_nonnull (widget); g_test_expect_message (HDY_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Can't add children of type GtkSwitch to HdyPreferencesPage"); gtk_container_add (GTK_CONTAINER (page), widget); g_test_assert_expected_messages (); } static void test_hdy_preferences_page_title (void) { g_autoptr (HdyPreferencesPage) page = NULL; page = g_object_ref_sink (HDY_PREFERENCES_PAGE (hdy_preferences_page_new ())); g_assert_nonnull (page); g_assert_null (hdy_preferences_page_get_title (page)); hdy_preferences_page_set_title (page, "Dummy title"); g_assert_cmpstr (hdy_preferences_page_get_title (page), ==, "Dummy title"); hdy_preferences_page_set_title (page, NULL); g_assert_null (hdy_preferences_page_get_title (page)); } static void test_hdy_preferences_page_icon_name (void) { g_autoptr (HdyPreferencesPage) page = NULL; page = g_object_ref_sink (HDY_PREFERENCES_PAGE (hdy_preferences_page_new ())); g_assert_nonnull (page); g_assert_null (hdy_preferences_page_get_icon_name (page)); hdy_preferences_page_set_icon_name (page, "dummy-icon-name"); g_assert_cmpstr (hdy_preferences_page_get_icon_name (page), ==, "dummy-icon-name"); hdy_preferences_page_set_icon_name (page, NULL); g_assert_null (hdy_preferences_page_get_icon_name (page)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/PreferencesPage/add", test_hdy_preferences_page_add); g_test_add_func("/Handy/PreferencesPage/title", test_hdy_preferences_page_title); g_test_add_func("/Handy/PreferencesPage/icon_name", test_hdy_preferences_page_icon_name); return g_test_run(); } libhandy-0.0.13/tests/test-preferences-row.c000066400000000000000000000026601360136463700207730ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_preferences_row_title (void) { g_autoptr (HdyPreferencesRow) row = NULL; row = g_object_ref_sink (HDY_PREFERENCES_ROW (hdy_preferences_row_new ())); g_assert_nonnull (row); g_assert_null (hdy_preferences_row_get_title (row)); hdy_preferences_row_set_title (row, "Dummy title"); g_assert_cmpstr (hdy_preferences_row_get_title (row), ==, "Dummy title"); hdy_preferences_row_set_title (row, NULL); g_assert_null (hdy_preferences_row_get_title (row)); } static void test_hdy_preferences_row_use_undeline (void) { g_autoptr (HdyPreferencesRow) row = NULL; row = g_object_ref_sink (HDY_PREFERENCES_ROW (hdy_preferences_row_new ())); g_assert_nonnull (row); g_assert_false (hdy_preferences_row_get_use_underline (row)); hdy_preferences_row_set_use_underline (row, TRUE); g_assert_true (hdy_preferences_row_get_use_underline (row)); hdy_preferences_row_set_use_underline (row, FALSE); g_assert_false (hdy_preferences_row_get_use_underline (row)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/PreferencesRow/title", test_hdy_preferences_row_title); g_test_add_func("/Handy/PreferencesRow/use_underline", test_hdy_preferences_row_use_undeline); return g_test_run(); } libhandy-0.0.13/tests/test-preferences-window.c000066400000000000000000000020161360136463700214660ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_preferences_window_add (void) { g_autoptr (HdyPreferencesWindow) window = NULL; HdyPreferencesPage *page; GtkWidget *widget; window = g_object_ref_sink (HDY_PREFERENCES_WINDOW (hdy_preferences_window_new ())); g_assert_nonnull (window); page = hdy_preferences_page_new (); g_assert_nonnull (page); gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (page)); widget = gtk_switch_new (); g_assert_nonnull (widget); g_test_expect_message (HDY_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Can't add children of type GtkSwitch to HdyPreferencesWindow"); gtk_container_add (GTK_CONTAINER (window), widget); g_test_assert_expected_messages (); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/PreferencesWindow/add", test_hdy_preferences_window_add); return g_test_run(); } libhandy-0.0.13/tests/test-search-bar.c000066400000000000000000000045141360136463700176740ustar00rootroot00000000000000/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_search_bar_add (void) { g_autoptr (HdySearchBar) bar = NULL; GtkWidget *entry; bar = g_object_ref_sink (HDY_SEARCH_BAR (hdy_search_bar_new ())); g_assert_nonnull (bar); entry = gtk_entry_new (); g_assert_nonnull (entry); gtk_container_add (GTK_CONTAINER (bar), entry); } static void test_hdy_search_bar_connect_entry (void) { g_autoptr (HdySearchBar) bar = NULL; GtkWidget *box, *entry; bar = g_object_ref_sink (HDY_SEARCH_BAR (hdy_search_bar_new ())); g_assert_nonnull (bar); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); g_assert_nonnull (box); entry = gtk_entry_new (); g_assert_nonnull (entry); gtk_container_add (GTK_CONTAINER (box), entry); gtk_container_add (GTK_CONTAINER (bar), box); hdy_search_bar_connect_entry (bar, GTK_ENTRY (entry)); } static void test_hdy_search_bar_search_mode (void) { g_autoptr (HdySearchBar) bar = NULL; bar = g_object_ref_sink (HDY_SEARCH_BAR (hdy_search_bar_new ())); g_assert_nonnull (bar); g_assert_false (hdy_search_bar_get_search_mode (bar)); hdy_search_bar_set_search_mode (bar, TRUE); g_assert_true (hdy_search_bar_get_search_mode (bar)); hdy_search_bar_set_search_mode (bar, FALSE); g_assert_false (hdy_search_bar_get_search_mode (bar)); } static void test_hdy_search_bar_show_close_button (void) { g_autoptr (HdySearchBar) bar = NULL; bar = g_object_ref_sink (HDY_SEARCH_BAR (hdy_search_bar_new ())); g_assert_nonnull (bar); g_assert_false (hdy_search_bar_get_show_close_button (bar)); hdy_search_bar_set_show_close_button (bar, TRUE); g_assert_true (hdy_search_bar_get_show_close_button (bar)); hdy_search_bar_set_show_close_button (bar, FALSE); g_assert_false (hdy_search_bar_get_show_close_button (bar)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/SearchBar/add", test_hdy_search_bar_add); g_test_add_func("/Handy/SearchBar/connect_entry", test_hdy_search_bar_connect_entry); g_test_add_func("/Handy/SearchBar/search_mode", test_hdy_search_bar_search_mode); g_test_add_func("/Handy/SearchBar/show_close_button", test_hdy_search_bar_show_close_button); return g_test_run(); } libhandy-0.0.13/tests/test-squeezer.c000066400000000000000000000113341360136463700175260ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_squeezer_homogeneous (void) { g_autoptr (HdySqueezer) squeezer = NULL; squeezer = g_object_ref_sink (hdy_squeezer_new ()); g_assert_nonnull (squeezer); g_assert_true (hdy_squeezer_get_homogeneous (squeezer)); hdy_squeezer_set_homogeneous (squeezer, FALSE); g_assert_false (hdy_squeezer_get_homogeneous (squeezer)); hdy_squeezer_set_homogeneous (squeezer, TRUE); g_assert_true (hdy_squeezer_get_homogeneous (squeezer)); } static void test_hdy_squeezer_transition_duration (void) { g_autoptr (HdySqueezer) squeezer = NULL; squeezer = g_object_ref_sink (hdy_squeezer_new ()); g_assert_nonnull (squeezer); g_assert_cmpuint (hdy_squeezer_get_transition_duration (squeezer), ==, 200); hdy_squeezer_set_transition_duration (squeezer, 400); g_assert_cmpuint (hdy_squeezer_get_transition_duration (squeezer), ==, 400); hdy_squeezer_set_transition_duration (squeezer, -1); g_assert_cmpuint (hdy_squeezer_get_transition_duration (squeezer), ==, G_MAXUINT); } static void test_hdy_squeezer_transition_type (void) { g_autoptr (HdySqueezer) squeezer = NULL; squeezer = g_object_ref_sink (hdy_squeezer_new ()); g_assert_nonnull (squeezer); g_assert_cmpuint (hdy_squeezer_get_transition_type (squeezer), ==, HDY_SQUEEZER_TRANSITION_TYPE_NONE); hdy_squeezer_set_transition_type (squeezer, HDY_SQUEEZER_TRANSITION_TYPE_CROSSFADE); g_assert_cmpuint (hdy_squeezer_get_transition_type (squeezer), ==, HDY_SQUEEZER_TRANSITION_TYPE_CROSSFADE); hdy_squeezer_set_transition_type (squeezer, HDY_SQUEEZER_TRANSITION_TYPE_NONE); g_assert_cmpuint (hdy_squeezer_get_transition_type (squeezer), ==, HDY_SQUEEZER_TRANSITION_TYPE_NONE); } static void test_hdy_squeezer_transition_running (void) { g_autoptr (HdySqueezer) squeezer = NULL; squeezer = g_object_ref_sink (hdy_squeezer_new ()); g_assert_nonnull (squeezer); g_assert_false (hdy_squeezer_get_transition_running (squeezer)); } static void test_hdy_squeezer_show_hide_child (void) { g_autoptr (HdySqueezer) squeezer = NULL; GtkWidget *child; squeezer = g_object_ref_sink (hdy_squeezer_new ()); g_assert_nonnull (squeezer); g_assert_null (hdy_squeezer_get_visible_child (squeezer)); child = gtk_label_new (""); gtk_container_add (GTK_CONTAINER (squeezer), child); g_assert_null (hdy_squeezer_get_visible_child (squeezer)); gtk_widget_show (child); g_assert (hdy_squeezer_get_visible_child (squeezer) == child); gtk_widget_hide (child); g_assert_null (hdy_squeezer_get_visible_child (squeezer)); gtk_widget_show (child); g_assert (hdy_squeezer_get_visible_child (squeezer) == child); gtk_container_remove (GTK_CONTAINER (squeezer), child); g_assert_null (hdy_squeezer_get_visible_child (squeezer)); } static void test_hdy_squeezer_interpolate_size (void) { g_autoptr (HdySqueezer) squeezer = NULL; squeezer = g_object_ref_sink (hdy_squeezer_new ()); g_assert_nonnull (squeezer); g_assert_false (hdy_squeezer_get_interpolate_size (squeezer)); hdy_squeezer_set_interpolate_size (squeezer, TRUE); g_assert_true (hdy_squeezer_get_interpolate_size (squeezer)); hdy_squeezer_set_interpolate_size (squeezer, FALSE); g_assert_false (hdy_squeezer_get_interpolate_size (squeezer)); } static void test_hdy_squeezer_child_enabled (void) { g_autoptr (HdySqueezer) squeezer = NULL; GtkWidget *child; squeezer = g_object_ref_sink (hdy_squeezer_new ()); g_assert_nonnull (squeezer); child = gtk_label_new (""); gtk_widget_show (child); gtk_container_add (GTK_CONTAINER (squeezer), child); g_assert_true (hdy_squeezer_get_child_enabled (squeezer, child)); hdy_squeezer_set_child_enabled (squeezer, child, FALSE); g_assert_false (hdy_squeezer_get_child_enabled (squeezer, child)); hdy_squeezer_set_child_enabled (squeezer, child, TRUE); g_assert_true (hdy_squeezer_get_child_enabled (squeezer, child)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/ViewSwitcher/homogeneous", test_hdy_squeezer_homogeneous); g_test_add_func("/Handy/ViewSwitcher/transition_duration", test_hdy_squeezer_transition_duration); g_test_add_func("/Handy/ViewSwitcher/transition_type", test_hdy_squeezer_transition_type); g_test_add_func("/Handy/ViewSwitcher/transition_running", test_hdy_squeezer_transition_running); g_test_add_func("/Handy/ViewSwitcher/show_hide_child", test_hdy_squeezer_show_hide_child); g_test_add_func("/Handy/ViewSwitcher/interpolate_size", test_hdy_squeezer_interpolate_size); g_test_add_func("/Handy/ViewSwitcher/child_enabled", test_hdy_squeezer_child_enabled); return g_test_run(); } libhandy-0.0.13/tests/test-string-utf8.c000066400000000000000000000024461360136463700200610ustar00rootroot00000000000000/* * Copyright (C) 2017 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_string_utf8_truncate(void) { GString *str; str = g_string_new("foo_bar"); g_assert_cmpstr("foo_", ==, hdy_string_utf8_truncate(str, 4)->str); g_assert_cmpint(4 , ==, str->len); str = g_string_new("☃f☃o☃o"); g_assert_cmpstr("☃f☃o", ==, hdy_string_utf8_truncate(str, 4)->str); g_assert_cmpint(8 , ==, str->len); str = g_string_new("☃f☃o☃o"); g_assert_cmpstr("☃f☃o☃o", ==, hdy_string_utf8_truncate(str, 10)->str); g_assert_cmpint(12, ==, str->len); str = g_string_new("☃f☃o☃o"); g_assert_cmpstr("", ==, hdy_string_utf8_truncate(str, 0)->str); g_assert_cmpint(0, ==, str->len); } static void test_hdy_string_utf8_len(void) { GString *str; str = g_string_new("foo_bar"); g_assert_cmpint(7, ==, hdy_string_utf8_len(str)); str = g_string_new("☃f☃o☃o"); g_assert_cmpint(6, ==, hdy_string_utf8_len(str)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/StringUTF8/truncate", test_hdy_string_utf8_truncate); g_test_add_func("/Handy/StringUTF8/len", test_hdy_string_utf8_len); return g_test_run(); } libhandy-0.0.13/tests/test-swipe-group.c000066400000000000000000000025211360136463700201420ustar00rootroot00000000000000/* * Copyright (C) 2019 Alexander Mikhaylenko * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_swipe_group_add_remove (void) { g_autoptr (HdySwipeGroup) group = NULL; g_autoptr (HdySwipeable) swipeable1 = NULL; g_autoptr (HdySwipeable) swipeable2 = NULL; group = hdy_swipe_group_new (); swipeable1 = HDY_SWIPEABLE (hdy_paginator_new ()); swipeable2 = HDY_SWIPEABLE (hdy_paginator_new ()); g_assert_cmpint (g_slist_length (hdy_swipe_group_get_swipeables (group)), ==, 0); hdy_swipe_group_add_swipeable (group, swipeable1); g_assert_cmpint (g_slist_length (hdy_swipe_group_get_swipeables (group)), ==, 1); hdy_swipe_group_add_swipeable (group, swipeable2); g_assert_cmpint (g_slist_length (hdy_swipe_group_get_swipeables (group)), ==, 2); hdy_swipe_group_remove_swipeable (group, swipeable2); g_assert_cmpint (g_slist_length (hdy_swipe_group_get_swipeables (group)), ==, 1); hdy_swipe_group_remove_swipeable (group, swipeable1); g_assert_cmpint (g_slist_length (hdy_swipe_group_get_swipeables (group)), ==, 0); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/SwipeGroup/add_remove", test_hdy_swipe_group_add_remove); return g_test_run(); } libhandy-0.0.13/tests/test-value-object.c000066400000000000000000000025061360136463700202440ustar00rootroot00000000000000/* * Copyright (C) 2019 Red Hat Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_value_object_init (void) { HdyValueObject *obj; GValue value = G_VALUE_INIT; gchar *str; g_value_init (&value, G_TYPE_STRING); g_value_set_string (&value, "asdfasdf"); obj = hdy_value_object_new (&value); g_assert_cmpstr (hdy_value_object_get_string (obj), ==, "asdfasdf"); g_clear_object (&obj); obj = hdy_value_object_new_string ("asdfasdf"); g_assert_cmpstr (hdy_value_object_get_string (obj), ==, "asdfasdf"); g_clear_object (&obj); obj = hdy_value_object_new_take_string (g_strdup ("asdfasdf")); g_assert_cmpstr (hdy_value_object_get_string (obj), ==, "asdfasdf"); g_clear_object (&obj); obj = hdy_value_object_new_collect (G_TYPE_STRING, "asdfasdf"); g_assert_cmpstr (hdy_value_object_get_string (obj), ==, "asdfasdf"); /* And check that _dup_string works too */ str = hdy_value_object_dup_string (obj); g_assert_cmpstr (str, ==, "asdfasdf"); g_clear_pointer (&str, g_free); g_clear_object (&obj); g_value_unset (&value); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/ValueObject/init", test_hdy_value_object_init); return g_test_run(); } libhandy-0.0.13/tests/test-view-switcher-bar.c000066400000000000000000000057231360136463700212320ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_view_switcher_bar_policy (void) { g_autoptr (HdyViewSwitcherBar) bar = NULL; bar = g_object_ref_sink (hdy_view_switcher_bar_new ()); g_assert_nonnull (bar); g_assert_cmpint (hdy_view_switcher_bar_get_policy (bar), ==, HDY_VIEW_SWITCHER_POLICY_NARROW); hdy_view_switcher_bar_set_policy (bar, HDY_VIEW_SWITCHER_POLICY_AUTO); g_assert_cmpint (hdy_view_switcher_bar_get_policy (bar), ==, HDY_VIEW_SWITCHER_POLICY_AUTO); hdy_view_switcher_bar_set_policy (bar, HDY_VIEW_SWITCHER_POLICY_WIDE); g_assert_cmpint (hdy_view_switcher_bar_get_policy (bar), ==, HDY_VIEW_SWITCHER_POLICY_WIDE); hdy_view_switcher_bar_set_policy (bar, HDY_VIEW_SWITCHER_POLICY_NARROW); g_assert_cmpint (hdy_view_switcher_bar_get_policy (bar), ==, HDY_VIEW_SWITCHER_POLICY_NARROW); } static void test_hdy_view_switcher_bar_icon_size (void) { g_autoptr (HdyViewSwitcherBar) bar = NULL; bar = g_object_ref_sink (hdy_view_switcher_bar_new ()); g_assert_nonnull (bar); g_assert_cmpint (hdy_view_switcher_bar_get_icon_size (bar), ==, GTK_ICON_SIZE_BUTTON); hdy_view_switcher_bar_set_icon_size (bar, GTK_ICON_SIZE_MENU); g_assert_cmpint (hdy_view_switcher_bar_get_icon_size (bar), ==, GTK_ICON_SIZE_MENU); hdy_view_switcher_bar_set_icon_size (bar, GTK_ICON_SIZE_BUTTON); g_assert_cmpint (hdy_view_switcher_bar_get_icon_size (bar), ==, GTK_ICON_SIZE_BUTTON); } static void test_hdy_view_switcher_bar_stack (void) { g_autoptr (HdyViewSwitcherBar) bar = NULL; GtkStack *stack; bar = g_object_ref_sink (hdy_view_switcher_bar_new ()); g_assert_nonnull (bar); stack = GTK_STACK (gtk_stack_new ()); g_assert_nonnull (stack); g_assert_null (hdy_view_switcher_bar_get_stack (bar)); hdy_view_switcher_bar_set_stack (bar, stack); g_assert (hdy_view_switcher_bar_get_stack (bar) == stack); hdy_view_switcher_bar_set_stack (bar, NULL); g_assert_null (hdy_view_switcher_bar_get_stack (bar)); } static void test_hdy_view_switcher_bar_reveal (void) { g_autoptr (HdyViewSwitcherBar) bar = NULL; bar = g_object_ref_sink (hdy_view_switcher_bar_new ()); g_assert_nonnull (bar); g_assert_false (hdy_view_switcher_bar_get_reveal (bar)); hdy_view_switcher_bar_set_reveal (bar, TRUE); g_assert_true (hdy_view_switcher_bar_get_reveal (bar)); hdy_view_switcher_bar_set_reveal (bar, FALSE); g_assert_false (hdy_view_switcher_bar_get_reveal (bar)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/ViewSwitcherBar/policy", test_hdy_view_switcher_bar_policy); g_test_add_func("/Handy/ViewSwitcherBar/icon_size", test_hdy_view_switcher_bar_icon_size); g_test_add_func("/Handy/ViewSwitcherBar/stack", test_hdy_view_switcher_bar_stack); g_test_add_func("/Handy/ViewSwitcherBar/reveal", test_hdy_view_switcher_bar_reveal); return g_test_run(); } libhandy-0.0.13/tests/test-view-switcher.c000066400000000000000000000065041360136463700204660ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #define HANDY_USE_UNSTABLE_API #include static void test_hdy_view_switcher_policy (void) { g_autoptr (HdyViewSwitcher) view_switcher = NULL; view_switcher = g_object_ref_sink (hdy_view_switcher_new ()); g_assert_nonnull (view_switcher); g_assert_cmpint (hdy_view_switcher_get_policy (view_switcher), ==, HDY_VIEW_SWITCHER_POLICY_AUTO); hdy_view_switcher_set_policy (view_switcher, HDY_VIEW_SWITCHER_POLICY_NARROW); g_assert_cmpint (hdy_view_switcher_get_policy (view_switcher), ==, HDY_VIEW_SWITCHER_POLICY_NARROW); hdy_view_switcher_set_policy (view_switcher, HDY_VIEW_SWITCHER_POLICY_WIDE); g_assert_cmpint (hdy_view_switcher_get_policy (view_switcher), ==, HDY_VIEW_SWITCHER_POLICY_WIDE); hdy_view_switcher_set_policy (view_switcher, HDY_VIEW_SWITCHER_POLICY_AUTO); g_assert_cmpint (hdy_view_switcher_get_policy (view_switcher), ==, HDY_VIEW_SWITCHER_POLICY_AUTO); } static void test_hdy_view_switcher_icon_size (void) { g_autoptr (HdyViewSwitcher) view_switcher = NULL; view_switcher = g_object_ref_sink (hdy_view_switcher_new ()); g_assert_nonnull (view_switcher); g_assert_cmpint (hdy_view_switcher_get_icon_size (view_switcher), ==, GTK_ICON_SIZE_BUTTON); hdy_view_switcher_set_icon_size (view_switcher, GTK_ICON_SIZE_MENU); g_assert_cmpint (hdy_view_switcher_get_icon_size (view_switcher), ==, GTK_ICON_SIZE_MENU); hdy_view_switcher_set_icon_size (view_switcher, GTK_ICON_SIZE_BUTTON); g_assert_cmpint (hdy_view_switcher_get_icon_size (view_switcher), ==, GTK_ICON_SIZE_BUTTON); } static void test_hdy_view_switcher_narrow_ellipsize (void) { g_autoptr (HdyViewSwitcher) view_switcher = NULL; view_switcher = g_object_ref_sink (hdy_view_switcher_new ()); g_assert_nonnull (view_switcher); g_assert_cmpint (hdy_view_switcher_get_narrow_ellipsize (view_switcher), ==, PANGO_ELLIPSIZE_NONE); hdy_view_switcher_set_narrow_ellipsize (view_switcher, PANGO_ELLIPSIZE_END); g_assert_cmpint (hdy_view_switcher_get_narrow_ellipsize (view_switcher), ==, PANGO_ELLIPSIZE_END); hdy_view_switcher_set_narrow_ellipsize (view_switcher, PANGO_ELLIPSIZE_NONE); g_assert_cmpint (hdy_view_switcher_get_narrow_ellipsize (view_switcher), ==, PANGO_ELLIPSIZE_NONE); } static void test_hdy_view_switcher_stack (void) { g_autoptr (HdyViewSwitcher) view_switcher = NULL; GtkStack *stack; view_switcher = g_object_ref_sink (hdy_view_switcher_new ()); g_assert_nonnull (view_switcher); stack = GTK_STACK (gtk_stack_new ()); g_assert_nonnull (stack); g_assert_null (hdy_view_switcher_get_stack (view_switcher)); hdy_view_switcher_set_stack (view_switcher, stack); g_assert (hdy_view_switcher_get_stack (view_switcher) == stack); hdy_view_switcher_set_stack (view_switcher, NULL); g_assert_null (hdy_view_switcher_get_stack (view_switcher)); } gint main (gint argc, gchar *argv[]) { gtk_test_init (&argc, &argv, NULL); hdy_init (&argc, &argv); g_test_add_func("/Handy/ViewSwitcher/policy", test_hdy_view_switcher_policy); g_test_add_func("/Handy/ViewSwitcher/icon_size", test_hdy_view_switcher_icon_size); g_test_add_func("/Handy/ViewSwitcher/narrow_ellipsize", test_hdy_view_switcher_narrow_ellipsize); g_test_add_func("/Handy/ViewSwitcher/stack", test_hdy_view_switcher_stack); return g_test_run(); }