Compare commits

..
9 Commits
Author SHA1 Message Date
shufeng2012 f6b9e64954 🍱 添加图标资源 2026-08-21 10:45:20 +08:00
shufeng2012 cf27e1e827 🏗️ 不用XML进行渲染了 2026-08-20 21:53:11 +08:00
shufeng2012 191a793a85 🍱 更新图标和名称 2026-08-18 21:10:34 +08:00
shufeng2012 47b7b34472 ✨ 完成menubar的显示了!!! 2026-08-17 16:24:39 +08:00
shufeng2012 ace2ea9a2c 🔥 删除gui相关 2026-08-16 19:44:05 +08:00
shufeng2012 88ccf893d9 📄 更新 2026-08-15 20:25:23 +08:00
shufeng2012 1e7b05672f 📄 更新LICENSE和copyright.txt 2026-08-15 20:13:23 +08:00
shufeng2012 c958f03b5b ⚰️ 删除冗余代码(win) 2026-08-15 19:52:13 +08:00
shufeng2012 28d9994f19 📄 更新LICENSE 2026-08-15 19:51:04 +08:00
634 changed files with 8388 additions and 35413 deletions

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
"string": [ "string": [
{ {
"name": "app_name", "name": "app_name",
"value": "ohos_xournalpp" "value": "Xournal++"
} }
] ]
} }
+1 -2
View File
@@ -290,8 +290,7 @@ 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 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. the "copyright" line and a pointer to where the full notice is found.
{description} Copyright (C) 2026 Hong Jiacan
Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
+339
View File
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 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.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, 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 or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
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 give any other recipients of the Program a copy of this License
along with the Program.
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 Program or any portion
of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
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 Program, 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 Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) 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; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, 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 executable. However, as a
special exception, the source code 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.
If distribution of executable or 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 counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program 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.
5. 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 Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program 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 to
this License.
7. 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 Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program 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 Program.
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.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program 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.
9. The Free Software Foundation may publish revised and/or new versions
of the 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 Program
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 Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, 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
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), 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 Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. 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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; 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.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+763
View File
@@ -0,0 +1,763 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: Xournal++
Upstream-Contact: https://github.com/xournalpp/xournalpp/issues/
Source: https://github.com/xournalpp/xournalpp/
Files: *
Copyright: 2010-2021, Andreas Butti <andreas.butti@gmail.com>
2012-2018, Wilson Brenna <wilson@wbrenna.homelinux.com>
2015-2016, Marek Pikuła <marek@pikula.tk>
2017-2019, Peter Klausing <peetCreative@users.noreply.github.com>
2018-2019, Moreno Razzoli <morrolinux@users.noreply.github.com>
2018-2019, Luca Errani <luca.errani1@gmail.com>
2019-2022, Ulrich Huber <ulrich@huberulrich.de>
2019-2019, Justin Jones <bjustjones@netscape.net>
2019-2023, Bryan Tan <Technius@users.noreply.github.com>
2019-2019, Justus Rossmeier <veecue@users.noreply.github.com>
2019-2025, Fabian Keßler <Fabian_Kessler@gmx.de>
2019-2020, Julius Lehmann <internet@devpi.de>
2020-2024, Tobias Hermann <idotobi@users.noreply.github.com>
2020-2025, Roland Lötscher <roland_loetscher@hotmail.com>
2021-2025, Benjamin Hennion <benjamin.hennion@wanadoo.fr>
2021-2022, Henry Heino <personalizedrefrigerator@gmail.com>
License: GPL-2+
Comment: Source of truth https://github.com/xournalpp/xournalpp/graphs/contributors
For a full and detailed list of contributors refer to the git log.
Files: src/core/control/xojfile/LoadHandlerHelper.cpp
src/core/control/xojfile/LoadHandlerHelper.h
src/core/gui/widgets/ZoomCallib.cpp
src/core/gui/widgets/ZoomCallib.h
Copyright: 2015, Xournal Team
License: GPL-2+
Comment: The files were committed by Andreas Butti initially who was active for both
Xournal and Xournal++
Files: ui/iconsColor-dark/*
ui/iconsColor-light/*
Copyright: 2020, Nararyans R.I.
License: GPL-3+
Comment: Source for the licensing information is https://github.com/xournalpp/xournalpp/issues/3103#issuecomment-847043386.
These icons where heavily inspired by https://github.com/elementary/icons
Files: ui/iconsColor-light/hicolor/scalable/actions/xopp-Tselect-pdf-text-area.svg
Copyright: 2021, aeghn
License: GPL-3+
Comment: See https://github.com/xournalpp/xournalpp/pull/3326
Files: ui/iconsColor-light/hicolor/scalable/actions/xopp-Tselect-pdf-text-hd.svg
Copyright: 2021, aeghn
License: GPL-3+
Comment: See https://github.com/xournalpp/xournalpp/pull/3326
Files: ui/iconsLucide-light/*
ui/iconsLucide-dark/*
Copyright: 2020, Lucide Contributors
License: ISC
Comment: See https://github.com/xournalpp/xournalpp/pull/4039
Files: ui/iconsLucide-light/hicolor/scalable/actions/xopp-floating-toolbox.svg
ui/iconsLucide-dark/hicolor/scalable/actions/xopp-floating-toolbox.svg
ui/iconsLucide-light/hicolor/scalable/actions/xopp-draw-spline.svg
ui/iconsLucide-dark/hicolor/scalable/actions/xopp-draw-spline.svg
Copyright: 2022, Friedrich Schriewer <friedrich.schriewer@gmx.net>
License: ISC
Comment: See https://github.com/xournalpp/xournalpp/pull/4039
Files: ui/iconsColor-dark/hicolor/scalable/actions/xopp-setsquare.svg
ui/iconsColor-dark/hicolor/scalable/actions/xopp-compass.svg
ui/iconsLucide-dark/hicolor/scalable/actions/xopp-setsquare.svg
ui/iconsLucide-dark/hicolor/scalable/actions/xopp-compass.svg
ui/iconsLucide-light/hicolor/scalable/actions/xopp-setsquare.svg
ui/iconsLucide-light/hicolor/scalable/actions/xopp-compass.svg
Copyright: 2022, Mario Franzbonenkamp
License: CC-BY-SA-4.0
Files: ui/pixmaps/com.github.xournalpp.xournalpp.svg
ui/pixmaps/com.github.xournalpp.xournalpp.png
ui/pixmaps/application-x-xojpp.svg
ui/pixmaps/application-x-xopp.svg
ui/pixmaps/application-x-xopt.svg
ui/pixmaps/gnome-mime-application-x-xopp.svg
ui/pixmaps/gnome-mime-application-x-xopt.svg
ui/pixmaps/xopt.svg
Copyright: 2020, Luya Tshimbalanga <luya@fedoraproject.org>
License: CC-BY-SA-4.0
Comment: See https://github.com/xournalpp/xournalpp/issues/3103#issuecomment-844556592
Files: cmake/include/Copyright.txt
Copyright: 2015, Marek Pikuła <marpirk@gmail.com>
Xournal++ team
License: BSD-3-clause
Files: cmake/include/GitRepo.cmake
Copyright: 2015, Marek Pikuła <marek@pikula.co>
License: BSD-3-clause
Files: cmake/include/clang-tidy.cmake
Copyright: 2000-2025 Kitware, Inc. and Contributors
License: BSD-3-clause
Files: cmake/find/Gettext.cmake
Copyright: 2012-2013, Valama development team
License: GPL-3+
Files: resources-templates/com.github.xournalpp.xournalpp.appdata.xml.in
Copyright: 2019, Tobias Mueller <tobiasmue@gnome.org>
License: CC0-1.0
Files: debian/docs
debian/package_description
debian/control
debian/changelog
debian/compat
debian/rules
debian/source/format
Copyright: 2007, Mathieu Bouchard <mbouchar@bioinfo.ulaval.ca>
2008, Lucas Nussbaum <lucas@lucas-nussbaum.net>
2009-2014, Carlo Segre <segre@debian.org>
2018-2019, Andreas Butti <andreas.butti@gmail.com>
2020-2021, Barak A. Pearlmutter <bap@debian.org>
License: GPL-2+
Files: debian/copyright
Copyright: 2018, Andreas Butti <andreasbutti@gmail.com>
License: GPL-2+
Files: plugins/Export/plugin.ini
Copyright: 2021, Roland Lötscher
License: GPL-2+
Files: plugins/ColorCycle/plugin.ini
plugins/LayerActions/plugin.ini
Copyright: 2020, Roland Lötscher
License: GPL-2+
Files: plugins/HighlightPosition/plugin.ini
Copyright: 2021, Michael J. Gruber
License: GPL-2+
Files: plugins/Example/plugin.ini
plugins/ToggleGrid/plugin.ini
Copyright: 2019, Andreas Butti
License: GPL-2+
Files: plugins/QuickScreenshot/plugin.ini
Copyright: 2020, Debbie Reynolds
License: GPL-2+
Files: plugins/BeamerPresentation/plugin.ini
Copyright: 2024, Lukas Heindl
License: GPL-2+
Files: plugins/FitToContent/plugin.ini
Copyright: 2022, Lukas Heindl
License: GPL-2+
Files: plugins/ImageActions/plugin.ini
Copyright: 2023, Roland Lötscher
License: GPL-2+
Files: plugins/SpaceForNotes/plugin.ini
Copyright: 2024, Lukas Heindl
License: GPL-2+
Files: src/core/gui/GladeSearchpath.h
src/core/gui/GladeSearchpath.cpp
Copyright: 2011, Andreas Butti
License: GPL-2+
Files: src/core/gui/dialog/IntEdLatexDialog.cpp
Copyright: 2013, Wilson Brenna
License: GPL-2+
Files: src/util/include/util/safe_casts.h
Copyright: 2020, Fabian Keßler <fabian_kessler@gmx.de>
License: BSL-1
License: GPL-2+
This program 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 2 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 <http://www.gnu.org/licenses/>.
.
On Debian systems, the full text of the GNU General Public License
version 2 can be found in the file `/usr/share/common-licenses/GPL-2'.
License: BSL-1
Boost Software License - Version 1.0 - August 17th, 2003
.
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
.
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
License: BSD-2-clause
Copy of text from https://opensource.org/licenses/BSD-2-Clause follows:
.
=======================================================================
.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License: BSD-3-clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
.
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
.
Neither the name of Adobe nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License: CC0-1.0
To the extent possible under law, the author(s) have dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
.
On Debian systems, the full copy of CC0-1.0 can be found at
/usr/share/common-licenses/CC0-1.0
License: GPL-3+
This program 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 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 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 <http://www.gnu.org/licenses/>.
.
On Debian systems the full text of the GNU General Public
License can be found in the `/usr/share/common-licenses/GPL-3' file.
License: LGPL-2+
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.1 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, see <https://www.gnu.org/licenses/>.
.
On Debian systems, the full text of the GNU Lesser General Public License
can be found in '/usr/share/common-licenses/LGPL-2.1'.
License: CC-BY-SA-4.0
See https://creativecommons.org/licenses/by-sa/4.0/
.
Copy of text from https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt follows:
.
Attribution-ShareAlike 4.0 International
.
=======================================================================
.
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
.
Using Creative Commons Public Licenses
.
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
.
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
.
=======================================================================
.
Creative Commons Attribution-ShareAlike 4.0 International Public
License
.
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-ShareAlike 4.0 International Public License ("Public
License"). To the extent this Public License may be interpreted as a
contract, You are granted the Licensed Rights in consideration of Your
acceptance of these terms and conditions, and the Licensor grants You
such rights in consideration of benefits the Licensor receives from
making the Licensed Material available under these terms and
conditions.
.
.
Section 1 -- Definitions.
.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
.
c. BY-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License.
.
d. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
.
e. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
.
f. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
.
g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this
Public License are Attribution and ShareAlike.
.
h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
.
i. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
.
j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
.
k. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
.
l. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
.
m. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
.
.
Section 2 -- Scope.
.
a. License grant.
.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
.
a. reproduce and Share the Licensed Material, in whole or
in part; and
.
b. produce, reproduce, and Share Adapted Material.
.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
.
3. Term. The term of this Public License is specified in Section
6(a).
.
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
.
5. Downstream recipients.
.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
.
b. Additional offer from the Licensor -- Adapted Material.
Every recipient of Adapted Material from You
automatically receives an offer from the Licensor to
exercise the Licensed Rights in the Adapted Material
under the conditions of the Adapter's License You apply.
.
c. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
.
b. Other rights.
.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
.
2. Patent and trademark rights are not licensed under this
Public License.
.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
.
.
Section 3 -- License Conditions.
.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
.
a. Attribution.
.
1. If You Share the Licensed Material (including in modified
form), You must:
.
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
.
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
.
ii. a copyright notice;
.
iii. a notice that refers to this Public License;
.
iv. a notice that refers to the disclaimer of
warranties;
.
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
.
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
.
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
.
b. ShareAlike.
.
In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
.
1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or
later, or a BY-SA Compatible License.
.
2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition
in any reasonable manner based on the medium, means, and
context in which You Share Adapted Material.
.
3. You may not offer or impose any additional or different terms
or conditions on, or apply any Effective Technological
Measures to, Adapted Material that restrict exercise of the
rights granted under the Adapter's License You apply.
.
.
Section 4 -- Sui Generis Database Rights.
.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
.
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
.
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material,
.
including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
.
.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
.
.
Section 6 -- Term and Termination.
.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
.
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
.
2. upon express reinstatement by the Licensor.
.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
.
.
Section 7 -- Other Terms and Conditions.
.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
.
.
Section 8 -- Interpretation.
.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
.
.
=======================================================================
.
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
.
Creative Commons may be contacted at creativecommons.org.
License: ISC
Copyright (c) 2020, Lucide Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -1,10 +0,0 @@
#include "Builder.h"
#include "util/StringUtils.h"
#include "GladeSearchpath.h" // for GladeSearchpath
#include "filesystem.h" // for path
Builder::Builder(GladeSearchpath* gladeSearchPath, const std::string& uiFile):
builder(gtk_builder_new_from_file(char_cast(gladeSearchPath->findFile("", uiFile).u8string().c_str())),
xoj::util::adopt) {}
-42
View File
@@ -1,42 +0,0 @@
/*
* Xournal++
*
* gtk_builder wrapper to parse ui files
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <iostream>
#include <string>
#include <gtk/gtk.h>
#include "util/Assert.h"
#include "util/raii/GObjectSPtr.h"
class GladeSearchpath;
class Builder final {
public:
Builder(GladeSearchpath* gladeSearchPath, const std::string& uiFile);
template <class GObjectType>
GObjectType* get(const std::string& name) {
GObjectType* res = static_cast<GObjectType*>(gtk_builder_get_object(builder.get(), name.c_str()));
xoj_assert_message(res, "ERROR: Builder::get: Could not find UI object: " + name + "\n");
return res;
}
inline GtkWidget* get(const std::string& name) { return GTK_WIDGET(get<GObject>(name)); }
private:
/**
* The Glade resources
*/
xoj::util::GObjectSPtr<GtkBuilder> builder;
};
@@ -1,39 +0,0 @@
#include "CreatePreviewImage.h"
#include "model/PageType.h" // for PageType
#include "util/Color.h" // for Color
#include "util/raii/CairoWrappers.h"
#include "util/raii/GObjectSPtr.h"
#include "view/background/BackgroundView.h" // for BackgroundView
namespace xoj::helper {
auto createPreviewImage(const PageType& pt) -> GtkWidget* {
const double zoom = 0.5;
xoj::util::CairoSurfaceSPtr surface(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PREVIEW_WIDTH, PREVIEW_HEIGHT),
xoj::util::adopt);
xoj::util::CairoSPtr crSPtr(cairo_create(surface.get()), xoj::util::adopt);
auto cr = crSPtr.get();
cairo_scale(cr, zoom, zoom);
auto bgView = xoj::view::BackgroundView::createRuled(PREVIEW_WIDTH / zoom, PREVIEW_HEIGHT / zoom, Colors::white, pt,
1. / zoom);
bgView->draw(cr);
cairo_identity_matrix(cr);
cairo_set_line_width(cr, 2);
cairo_set_source_rgb(cr, 0.8, 0.8, 0.8);
cairo_move_to(cr, 0, 0);
cairo_line_to(cr, PREVIEW_WIDTH, 0);
cairo_line_to(cr, PREVIEW_WIDTH, PREVIEW_HEIGHT);
cairo_line_to(cr, 0, PREVIEW_HEIGHT);
cairo_line_to(cr, 0, 0);
cairo_stroke(cr);
xoj::util::GObjectSPtr<GdkPixbuf> pixbuf(
gdk_pixbuf_get_from_surface(surface.get(), 0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT), xoj::util::adopt);
return gtk_image_new_from_pixbuf(pixbuf.get());
}
}; // namespace xoj::helper
@@ -1,26 +0,0 @@
/*
* Xournal++
*
* Helper function
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <gtk/gtk.h> // for GtkWidget
class PageType;
namespace xoj::helper {
constexpr auto PREVIEW_WIDTH = 100;
constexpr auto PREVIEW_HEIGHT = 141;
/**
* @brief Create a GtkImage containing a miniature of the given (standard) page type
* The returned widget is a floating ref.
*/
auto createPreviewImage(const PageType& pt) -> GtkWidget*;
}; // namespace xoj::helper
@@ -1,217 +0,0 @@
#include "FloatingToolbox.h"
#include <algorithm> // for max
#include <memory> // for allocator
#include <gdk/gdk.h> // for GdkRectangle, GDK_LEAVE_...
#include <glib-object.h> // for G_CALLBACK, g_signal_con...
#include "control/Control.h" // for Control
#include "control/ToolEnums.h" // for TOOL_FLOATING_TOOLBOX
#include "control/settings/ButtonConfig.h" // for ButtonConfig
#include "control/settings/Settings.h" // for Settings
#include "control/settings/SettingsEnums.h" // for BUTTON_COUNT
#include "util/glib_casts.h"
#include "MainWindow.h" // for MainWindow
#include "ToolbarDefinitions.h" // for ToolbarEntryDefintion
FloatingToolbox::FloatingToolbox(MainWindow* theMainWindow, GtkOverlay* overlay) {
this->mainWindow = theMainWindow;
this->floatingToolbox = theMainWindow->get("floatingToolbox");
this->floatingToolboxX = 200;
this->floatingToolboxY = 200;
this->floatingToolboxState = recalcSize;
gtk_overlay_add_overlay(overlay, this->floatingToolbox);
gtk_overlay_set_overlay_pass_through(overlay, this->floatingToolbox, true);
gtk_widget_add_events(this->floatingToolbox, GDK_LEAVE_NOTIFY_MASK);
g_signal_connect(this->floatingToolbox, "leave-notify-event",
xoj::util::wrap_for_g_callback_v<handleLeaveFloatingToolbox>, this);
// position overlay widgets
g_signal_connect(overlay, "get-child-position", xoj::util::wrap_for_g_callback_v<getOverlayPosition>, this);
}
FloatingToolbox::~FloatingToolbox() = default;
void FloatingToolbox::show(int x, int y) {
this->floatingToolboxX = x;
this->floatingToolboxY = y;
this->show();
}
/****
* floatingToolboxActivated
* True if the user has:
* assigned a mouse or stylus button to bring up the floatingToolbox;
* or enabled tapAction and Show FloatingToolbox( prefs->DrawingArea->ActionOnToolTap );
* or put tools in the FloatingToolbox.
*
*/
auto FloatingToolbox::floatingToolboxActivated() -> bool {
Settings* settings = this->mainWindow->getControl()->getSettings();
ButtonConfig* cfg = nullptr;
// check if any buttons assigned to bring up toolbox
for (unsigned int id = 0; id < BUTTON_COUNT; id++) {
cfg = settings->getButtonConfig(id);
if (cfg->getAction() == TOOL_FLOATING_TOOLBOX) {
return true; // return true
}
}
// check if user can show Floating Menu with tap.
if (settings->getDoActionOnStrokeFiltered() && settings->getStrokeFilterEnabled()) {
return true; // return true
}
return this->hasWidgets();
}
auto FloatingToolbox::hasWidgets() -> bool {
for (int index = TBFloatFirst; index <= TBFloatLast; index++) {
GtkToolbar* toolbar1 = GTK_TOOLBAR(this->mainWindow->get(TOOLBAR_DEFINITIONS[index].guiName));
if (gtk_toolbar_get_n_items(toolbar1) > 0) {
return true;
}
}
return false;
}
void FloatingToolbox::showForConfiguration() {
if (this->floatingToolboxActivated()) // Do not show if not being used - at least while experimental.
{
GtkWidget* boxContents = this->mainWindow->get("boxContents");
gint wx = 0, wy = 0;
gtk_widget_translate_coordinates(boxContents, gtk_widget_get_toplevel(boxContents), 0, 0, &wx, &wy);
this->floatingToolboxX = wx + 40; // when configuration state these are
this->floatingToolboxY = wy + 40; // topleft coordinates( otherwise center).
this->floatingToolboxState = configuration;
this->show();
}
}
void FloatingToolbox::show() {
gtk_widget_show_all(this->floatingToolbox);
gtk_widget_set_visible(this->mainWindow->get("labelFloatingToolbox"), this->floatingToolboxState == configuration);
gtk_widget_set_visible(this->mainWindow->get("showIfEmpty"),
this->floatingToolboxState != configuration && !hasWidgets());
}
void FloatingToolbox::hide() {
if (this->floatingToolboxState == configuration) {
this->floatingToolboxState = recalcSize;
}
gtk_widget_hide(this->floatingToolbox);
}
void FloatingToolbox::flagRecalculateSizeRequired() { this->floatingToolboxState = recalcSize; }
/**
* getOverlayPosition - this is how we position the widget in the overlay under the mouse
*
* The requested location is communicated via the FloatingToolbox member variables:
* ->floatingToolbox, so we can operate on the right widget
* ->floatingToolboxState, are we configuring, resizing or just moving
* ->floatingToolboxX, where to display
* ->floatingToolboxY.
*
*/
auto FloatingToolbox::getOverlayPosition(GtkOverlay* overlay, GtkWidget* widget, GdkRectangle* allocation,
FloatingToolbox* self) -> bool {
if (widget != self->floatingToolbox) {
return false;
}
gtk_widget_get_allocation(widget, allocation);
if (self->floatingToolboxState != noChange || allocation->height < 2) {
GtkRequisition natural;
gtk_widget_get_preferred_size(widget, nullptr, &natural);
allocation->width = natural.width;
allocation->height = natural.height;
}
// Get scrolled window for boundary clamping
GtkWidget* mainBox = self->mainWindow->get("mainBox");
GtkWidget* boxContents = self->mainWindow->get("boxContents");
GtkWidget* scrolledWindow = nullptr;
GList* children = gtk_container_get_children(GTK_CONTAINER(boxContents));
if (children != nullptr) {
scrolledWindow = GTK_WIDGET(children->data);
g_list_free(children);
}
if (scrolledWindow == nullptr) {
// Fallback: no clamping if scrolled window not found
allocation->x = self->floatingToolboxX - allocation->width / 2;
allocation->y = self->floatingToolboxY - allocation->height / 2;
self->floatingToolboxState = noChange;
return true;
}
// Get scrolled window position relative to mainBox
gint scrollX, scrollY;
gtk_widget_translate_coordinates(scrolledWindow, mainBox, 0, 0, &scrollX, &scrollY);
GtkAllocation scrollAllocation;
gtk_widget_get_allocation(scrolledWindow, &scrollAllocation);
switch (self->floatingToolboxState) {
case recalcSize:
[[fallthrough]];
case noChange: {
int centerX = self->floatingToolboxX - allocation->width / 2;
int centerY = self->floatingToolboxY - allocation->height / 2;
// Clamp to scrolled window bounds with margin
constexpr int margin = 10;
int minX = scrollX + margin;
int maxX = scrollX + scrollAllocation.width - allocation->width - margin;
int minY = scrollY + margin;
int maxY = scrollY + scrollAllocation.height - allocation->height - margin;
// Ensure valid clamp bounds when toolbox is larger than viewport
maxX = std::max(maxX, minX);
maxY = std::max(maxY, minY);
allocation->x = std::clamp(centerX, minX, maxX);
allocation->y = std::clamp(centerY, minY, maxY);
self->floatingToolboxState = noChange;
break;
}
case configuration:
allocation->x = self->floatingToolboxX;
allocation->y = self->floatingToolboxY;
allocation->width = std::max(allocation->width + 32, 50);
allocation->height = std::max(allocation->height, 50);
break;
}
return true;
}
bool FloatingToolbox::handleLeaveFloatingToolbox(GtkWidget* floatingToolbox, GdkEvent* event, FloatingToolbox* self) {
if (floatingToolbox == self->floatingToolbox) {
if (self->floatingToolboxState != configuration) {
self->hide();
}
return true;
}
return false;
}
@@ -1,94 +0,0 @@
/*
* Xournal++
*
* The Floating Toolbox
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <gdk/gdk.h> // for GdkEvent, GdkRectangle
#include <glib.h> // for gboolean
#include <gtk/gtk.h> // for GtkWidget, GtkOverlay
class MainWindow;
enum FloatingToolBoxState { recalcSize = 0, configuration, noChange };
/************************
* FloatingToolbox:
* Uses a GtkOverlay to show a widget containing toolbars.
* The getOverlayPosition callback is used to position the toolbox widget relative to the users input device.
* The ui/main.glade file defines the floatingToolbox as a GtkRevealer widget - attempts to use another widget
* had issues with leave-notify-event.
*/
class FloatingToolbox {
public:
FloatingToolbox(MainWindow* theMainWindow, GtkOverlay* overlay);
virtual ~FloatingToolbox();
public:
/**
* show(x,y): Show Toolbox at centered at x,y relative to main window.
*/
void show(int x, int y);
/**
* showForConfiguration
* i.e. appear in fixed position in top left, extra space and do not hide automatically.
*/
void showForConfiguration();
void hide();
/**
* flagRecalculateSizeRequired(): trigger recalc size on next getOverlayPosition. Used when new toolbars loaded.
*/
void flagRecalculateSizeRequired();
private:
/**
* Callback for positioning overlayed floating menu
*/
static bool getOverlayPosition(GtkOverlay* overlay, GtkWidget* widget, GdkRectangle* alloc, FloatingToolbox* self);
/**
* Callback to hide floating Toolbar when mouse leaves it
*/
static bool handleLeaveFloatingToolbox(GtkWidget* floatingToolbox, GdkEvent* event, FloatingToolbox* self);
/**
* Show the Floating Toolbox
* ... but hide some labels depending on conditions.
*/
void show();
/**
* check if user has assigned a button to activate, or has put tools in the FloatingToolbox.
*/
bool floatingToolboxActivated();
/**
* Whether there are items in the toolbox.
* Note this includes non-tools such as spacers and separators.
*/
bool hasWidgets();
private:
MainWindow* mainWindow;
GtkWidget* floatingToolbox;
/**
* Communicating with getOverlayPosition callback
* */
int floatingToolboxX = 0;
int floatingToolboxY = 0;
FloatingToolBoxState floatingToolboxState = recalcSize;
};
@@ -1,57 +0,0 @@
#include "GladeGui.h"
#include <cstdlib> // for exit
#include <glib-object.h> // for g_object_unref
#include <glib.h> // for g_error_free, GError, g_warning
#include "util/PlaceholderString.h" // for PlaceholderString
#include "util/StringUtils.h" // for char_cast
#include "util/XojMsgBox.h" // for XojMsgBox
#include "util/i18n.h" // for FS, _F
#include "GladeSearchpath.h" // for GladeSearchpath
#include "filesystem.h" // for path
GladeGui::GladeGui(GladeSearchpath* gladeSearchPath, const std::string& glade, const std::string& mainWnd) {
this->gladeSearchPath = gladeSearchPath;
auto filepath = this->gladeSearchPath->findFile("", glade);
GError* error = nullptr;
builder.reset(gtk_builder_new(), xoj::util::adopt);
if (!gtk_builder_add_from_file(builder.get(), char_cast(filepath.u8string().c_str()), &error)) {
std::string msg = FS(_F("Error loading glade file \"{1}\" (try to load \"{2}\")") % glade %
char_cast(filepath.u8string()));
if (error != nullptr) {
msg += "\n";
msg += error->message;
g_error_free(error);
}
XojMsgBox::showErrorAndQuit(msg, -1);
}
this->window = get(mainWnd);
}
GladeGui::~GladeGui() {
if (!gtk_widget_get_parent(window)) {
gtk_widget_destroy(window);
}
}
auto GladeGui::get(const std::string& name) -> GtkWidget* {
GtkWidget* w = GTK_WIDGET(gtk_builder_get_object(builder.get(), name.c_str()));
if (w == nullptr) {
g_warning("GladeGui::get: Could not find glade Widget: \"%s\"", name.c_str());
}
return w;
}
auto GladeGui::getWindow() const -> GtkWidget* { return this->window; }
auto GladeGui::getGladeSearchPath() const -> GladeSearchpath* { return this->gladeSearchPath; }
auto GladeGui::getBuilder() const -> GtkBuilder* { return this->builder.get(); }
@@ -1,55 +0,0 @@
/*
* Xournal++
*
* Abstract GUI class, which loads the glade objects
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <string> // for string
#include <gdk/gdk.h> // for GdkWindow
#include <gtk/gtk.h> // for GtkWidget, GtkWindow, GtkBuilder
#include "util/raii/GObjectSPtr.h"
class GladeSearchpath;
class GladeGui {
public:
GladeGui(GladeSearchpath* gladeSearchPath, const std::string& glade, const std::string& mainWnd);
virtual ~GladeGui();
virtual void show(GtkWindow* parent) = 0;
GtkWidget* get(const std::string& name);
void setThemePath(std::string themePath);
GtkWidget* getWindow() const;
GladeSearchpath* getGladeSearchPath() const;
GtkBuilder* getBuilder() const;
private:
/**
* The Glade resources
*/
xoj::util::GObjectSPtr<GtkBuilder> builder;
/**
* Our search paths
*/
GladeSearchpath* gladeSearchPath;
protected:
/**
* This window
*/
GtkWidget* window = nullptr;
};
@@ -1,52 +0,0 @@
/*
* Xournal++
*
* Search directory for icons and Glade files
*
* @author andreas
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#include "GladeSearchpath.h"
GladeSearchpath::GladeSearchpath() = default;
GladeSearchpath::~GladeSearchpath() { directories.clear(); }
auto GladeSearchpath::findFile(fs::path const& subdir, fs::path const& file) const -> fs::path {
fs::path filepath;
if (subdir.empty()) {
filepath = file;
} else {
filepath = subdir / file;
}
// We step through each directory to find it.
for (const auto& dir: directories) {
auto pathname = dir / filepath;
if (fs::exists(pathname)) {
return pathname;
}
}
return fs::path{};
}
/**
* @return The first search path
*/
auto GladeSearchpath::getFirstSearchPath() const -> fs::path {
if (this->directories.empty()) {
return {};
}
return this->directories[0];
}
/**
* Use this function to set the directory containing installed pixmaps and Glade XML files.
*/
void GladeSearchpath::addSearchDirectory(fs::path const& directory) { this->directories.push_back(directory); }
@@ -1,41 +0,0 @@
/*
* Xournal++
*
* Search directory for icons and Glade files
*
* @author andreas
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <vector> // for vector
#include "filesystem.h" // for path
class GladeSearchpath {
public:
GladeSearchpath();
virtual ~GladeSearchpath();
public:
void addSearchDirectory(fs::path const& directory);
/**
* Searches for a path, return the path, an empty string if not found
*/
fs::path findFile(fs::path const& subdir, fs::path const& file) const;
/**
* @return The first search path
*/
fs::path getFirstSearchPath() const;
private:
/**
* Search directory for icons and Glade files
*/
std::vector<fs::path> directories;
};
@@ -1,15 +0,0 @@
#include "IconNameHelper.h"
#include <gtk/gtk.h> // for gtk_icon_theme_get_default
#include "control/settings/Settings.h" // for Settings
IconNameHelper::IconNameHelper(Settings* settings): settings(settings) {}
auto IconNameHelper::iconName(const char* icon) const -> std::string {
std::string xoppName = std::string("xopp-") + icon;
auto iconName = this->settings->areStockIconsUsed() && gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), icon) ?
std::string(icon) :
xoppName;
return iconName;
}
@@ -1,27 +0,0 @@
/*
* Xournal++
*
* Helper which allows to switch between Xournal++ icons and stock icons
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <string>
class Settings;
class IconNameHelper final {
public:
IconNameHelper(Settings* settings);
protected:
const Settings* settings;
public:
std::string iconName(const char* icon) const;
};
-432
View File
@@ -1,432 +0,0 @@
#include "Layout.h"
#include <algorithm> // for max, lower_bound, transform
#include <cmath> // for abs
#include <iterator> // for begin, end, distance
#include <numeric> // for accumulate
#include <optional> // for optional
#include <type_traits> // for make_signed_t, remove_referen...
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
#include "control/Control.h" // for Control
#include "control/settings/Settings.h" // for Settings
#include "gui/LayoutMapper.h" // for LayoutMapper, GridPosition
#include "gui/PageView.h" // for XojPageView
#include "gui/scroll/ScrollHandling.h" // for ScrollHandling
#include "model/Document.h" // for Document
#include "util/Rectangle.h" // for Rectangle
#include "util/safe_casts.h" // for strict_cast, as_signed, as_si...
#include "XournalView.h" // for XournalView
using xoj::util::Rectangle;
/**
* Padding outside the pages, including shadow
*/
constexpr auto const XOURNAL_PADDING = 10;
/**
* Allowance for shadow between page pairs in paired page mode
*/
constexpr auto const XOURNAL_ROOM_FOR_SHADOW = 3;
/**
* Padding between the pages
*/
constexpr auto const XOURNAL_PADDING_BETWEEN = 15;
Layout::Layout(XournalView* view, ScrollHandling* scrollHandling): view(view), scrollHandling(scrollHandling) {
g_signal_connect(scrollHandling->getHorizontal(), "value-changed", G_CALLBACK(horizontalScrollChanged), this);
g_signal_connect(scrollHandling->getVertical(), "value-changed", G_CALLBACK(verticalScrollChanged), this);
lastScrollHorizontal = gtk_adjustment_get_value(scrollHandling->getHorizontal());
lastScrollVertical = gtk_adjustment_get_value(scrollHandling->getVertical());
}
void Layout::horizontalScrollChanged(GtkAdjustment* adjustment, Layout* layout) {
Layout::checkScroll(adjustment, layout->lastScrollHorizontal);
layout->updateVisibility();
}
void Layout::verticalScrollChanged(GtkAdjustment* adjustment, Layout* layout) {
Layout::checkScroll(adjustment, layout->lastScrollVertical);
layout->updateVisibility();
layout->maybeAddLastPage(layout);
}
void Layout::maybeAddLastPage(Layout* layout) {
auto* control = this->view->getControl();
auto* settings = control->getSettings();
if (settings->getEmptyLastPageAppend() == EmptyLastPageAppendType::OnScrollToEndOfLastPage) {
// If the layout is 5px away from the end of the last page
if (std::abs((layout->getMinimalHeight() - layout->getVisibleRect().y) - layout->getVisibleRect().height) < 5) {
auto* doc = control->getDocument();
doc->lock_shared();
auto pdfPageCount = doc->getPdfPageCount();
auto lastPage = doc->getPageCount() - 1;
doc->unlock_shared();
if (pdfPageCount == 0) {
auto currentPage = control->getCurrentPageNo();
if (currentPage == lastPage) {
control->insertNewPage(currentPage + 1, true);
}
}
}
}
}
void Layout::checkScroll(GtkAdjustment* adjustment, double& lastScroll) {
lastScroll = gtk_adjustment_get_value(adjustment);
}
void Layout::updateVisibility() {
Rectangle visRect = getVisibleRect();
// step through every possible page position and update using p->setIsVisible()
// Using initial grid aprox speeds things up by a factor of 5. See previous git check-in for specifics.
int x1 = 0;
int y1 = 0;
// Data to select page based on visibility
std::optional<size_t> mostPageNr;
double mostPagePercent = 0;
for (size_t row = 0; row < this->rowYStart.size(); ++row) {
auto y2 = as_signed_strict(this->rowYStart[row]);
for (size_t col = 0; col < this->colXStart.size(); ++col) {
auto x2 = as_signed_strict(this->colXStart[col]);
auto optionalPage = this->mapper.at({col, row});
if (optionalPage) // a page exists at this grid location
{
auto& pageView = this->view->viewPages[*optionalPage];
// check if grid location is visible as an aprox for page visiblity:
if (!(visRect.x > x2 || visRect.x + visRect.width < x1) // visrect not outside current row/col
&& !(visRect.y > y2 || visRect.y + visRect.height < y1)) {
// now use exact check of page itself:
// visrect not outside current page dimensions:
auto const& pageRect = pageView->getRect();
if (auto intersection = pageRect.intersects(visRect); intersection) {
pageView->setIsVisible(true);
// Set the selected page
double percent = intersection->area() / pageRect.area();
if (percent > mostPagePercent) {
mostPageNr = *optionalPage;
mostPagePercent = percent;
}
}
} else {
pageView->setIsVisible(false);
}
}
x1 = x2;
}
y1 = y2;
x1 = 0;
}
if (mostPageNr) {
this->view->getControl()->firePageSelected(*mostPageNr);
}
}
auto Layout::getVisibleRect() -> Rectangle<double> {
return Rectangle(gtk_adjustment_get_value(scrollHandling->getHorizontal()),
gtk_adjustment_get_value(scrollHandling->getVertical()),
gtk_adjustment_get_page_size(scrollHandling->getHorizontal()),
gtk_adjustment_get_page_size(scrollHandling->getVertical()));
}
/**
* adds the addend to base if the predicate is true
*/
[[maybe_unused]] constexpr auto sumIf = [](auto base, auto addend, bool predicate) {
if constexpr (std::is_signed_v<decltype(base)> || std::is_signed_v<decltype(addend)>) {
using RT = std::make_signed_t<decltype(base + addend)>;
if (predicate) {
return RT(base) + RT(addend);
}
return RT(base);
} else if constexpr (!(std::is_signed_v<decltype(base)> || std::is_signed_v<decltype(addend)>)) {
using RT = decltype(base + addend);
if (predicate) {
return RT(base) + RT(addend);
}
return RT(base);
}
};
void Layout::recalculate_int() const {
auto* settings = view->getControl()->getSettings();
auto len = view->viewPages.size();
mapper.configureFromSettings(len, settings);
auto colCount = mapper.getColumns();
auto rowCount = mapper.getRows();
pc.widthCols.assign(colCount, 0);
pc.heightRows.assign(rowCount, 0);
for (size_t pageIdx{}; pageIdx < len; ++pageIdx) {
auto const& raster_p = mapper.at(pageIdx); // auto [c, r] raster = mapper.at();
auto const& c = raster_p.col;
auto const& r = raster_p.row;
auto& v = view->viewPages[pageIdx];
pc.widthCols[c] = std::max(pc.widthCols[c], v->getDisplayWidthDouble());
pc.heightRows[r] = std::max(pc.heightRows[r], v->getDisplayHeightDouble());
}
// add space around the entire page area to accommodate older Wacom tablets with limited sense area.
auto vPadding = 2 * XOURNAL_PADDING;
if (settings->getUnlimitedScrolling()) {
vPadding += 2 * static_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getVertical()));
} else if (settings->getAddVerticalSpace()) {
vPadding += settings->getAddVerticalSpaceAmountAbove();
vPadding += settings->getAddVerticalSpaceAmountBelow();
}
auto hPadding = 2 * XOURNAL_PADDING;
if (settings->getUnlimitedScrolling()) {
hPadding += 2 * static_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getHorizontal()));
} else if (settings->getAddHorizontalSpace()) {
hPadding += settings->getAddHorizontalSpaceAmountLeft();
hPadding += settings->getAddHorizontalSpaceAmountRight();
}
pc.minWidth = as_unsigned(hPadding + as_signed_strict((pc.widthCols.size() - 1) * XOURNAL_PADDING_BETWEEN));
pc.minHeight = as_unsigned(vPadding + as_signed_strict((pc.heightRows.size() - 1) * XOURNAL_PADDING_BETWEEN));
pc.minWidth = floor_cast<size_t>(std::accumulate(begin(pc.widthCols), end(pc.widthCols), double(pc.minWidth)));
pc.minHeight = floor_cast<size_t>(std::accumulate(begin(pc.heightRows), end(pc.heightRows), double(pc.minHeight)));
pc.valid = true;
}
void Layout::recalculate() {
pc.valid = false;
gtk_widget_queue_resize(view->getWidget());
}
void Layout::layoutPages(int width, int height) {
std::lock_guard g{pc.m};
if (!pc.valid) {
recalculate_int();
}
// Todo: remove, just a hack-hotfix
scrollHandling->setLayoutSize(std::max(width, strict_cast<int>(this->pc.minWidth)),
std::max(height, strict_cast<int>(this->pc.minHeight)));
size_t const len = this->view->viewPages.size();
Settings* settings = this->view->getControl()->getSettings();
// get from mapper (some may have changed to accommodate paired setting etc.)
bool const isPairedPages = this->mapper.isPairedPages();
auto const rows = this->pc.heightRows.size();
auto const columns = this->pc.widthCols.size();
// add space around the entire page area to accommodate older Wacom tablets with limited sense area.
auto v_padding = XOURNAL_PADDING;
if (settings->getUnlimitedScrolling()) {
v_padding += ceil_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getVertical()));
} else if (settings->getAddVerticalSpace()) {
v_padding += settings->getAddVerticalSpaceAmountAbove();
}
auto h_padding = XOURNAL_PADDING;
if (settings->getUnlimitedScrolling()) {
h_padding += ceil_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getHorizontal()));
} else if (settings->getAddHorizontalSpace()) {
h_padding += settings->getAddHorizontalSpaceAmountLeft();
}
auto const centeringXBorder = (width - as_signed(pc.minWidth)) / 2;
auto const centeringYBorder = (height - as_signed(pc.minHeight)) / 2;
using SBig = decltype(as_signed(h_padding * centeringXBorder));
auto const borderX = static_cast<double>(std::max<SBig>(h_padding, centeringXBorder));
auto const borderY = static_cast<double>(std::max<SBig>(v_padding, centeringYBorder));
// initialize here and x again in loop below.
auto x = borderX;
auto y = borderY;
// Iterate over ALL possible rows and columns.
// We don't know which page, if any, is to be displayed in each row, column - ask the mapper object!
// Then assign that page coordinates with center, left or right justify within row,column grid cell as required.
for (size_t r = 0; r < rows; r++) {
for (size_t c = 0; c < columns; c++) {
auto optionalPage = this->mapper.at({c, r});
if (optionalPage) {
auto& v = this->view->viewPages[*optionalPage];
v->setMappedRowCol(strict_cast<int>(r),
strict_cast<int>(c)); // store row and column for e.g. proper arrow key navigation
auto vDisplayWidth = v->getDisplayWidthDouble();
{
auto paddingLeft = 0.0;
auto paddingRight = 0.0;
auto columnPadding = this->pc.widthCols[c] - vDisplayWidth;
if (isPairedPages && len > 1) {
// pair pages mode
if (c % 2 == 0) {
// align right
paddingLeft = XOURNAL_PADDING_BETWEEN - XOURNAL_ROOM_FOR_SHADOW + columnPadding;
paddingRight = XOURNAL_ROOM_FOR_SHADOW;
} else { // align left
paddingLeft = XOURNAL_ROOM_FOR_SHADOW;
paddingRight = XOURNAL_PADDING_BETWEEN - XOURNAL_ROOM_FOR_SHADOW + columnPadding;
}
} else { // not paired page mode - center
paddingLeft = XOURNAL_PADDING_BETWEEN / 2.0 + columnPadding / 2.0; // center justify
paddingRight = XOURNAL_PADDING_BETWEEN - paddingLeft + columnPadding;
}
x += paddingLeft;
v->setX(floor_cast<int>(x)); // set the page position
v->setY(floor_cast<int>(y));
x += vDisplayWidth + paddingRight;
}
} else {
x += this->pc.widthCols[c] + XOURNAL_PADDING_BETWEEN;
}
}
x = borderX;
y += this->pc.heightRows[r] + XOURNAL_PADDING_BETWEEN;
}
this->colXStart.resize(this->pc.widthCols.size());
this->rowYStart.resize(this->pc.heightRows.size());
// accumulated - absolute pixel location for use by getViewAt() and updateVisibility()
auto totalWidth = borderX;
std::transform(
begin(this->pc.widthCols), end(this->pc.widthCols), begin(this->colXStart), [&totalWidth](auto&& widthCol) {
return strict_cast<std::remove_reference_t<decltype(widthCol)>>(totalWidth +=
widthCol + XOURNAL_PADDING_BETWEEN);
});
auto totalHeight = borderY;
std::transform(begin(this->pc.heightRows), end(this->pc.heightRows), begin(this->rowYStart),
[&totalHeight](auto&& heightRow) {
return strict_cast<std::remove_reference_t<decltype(heightRow)>>(
(totalHeight += heightRow + XOURNAL_PADDING_BETWEEN));
});
}
auto Layout::getPaddingAbovePage(size_t pageIndex) const -> int {
const Settings* settings = this->view->getControl()->getSettings();
// User-configured padding above all pages.
auto paddingAbove = XOURNAL_PADDING;
if (settings->getUnlimitedScrolling()) {
paddingAbove += ceil_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getVertical()));
} else if (settings->getAddVerticalSpace()) {
paddingAbove += settings->getAddVerticalSpaceAmountAbove();
}
// (x, y) coordinate pair gives grid indices. This handles paired pages and different page layouts for us.
auto pageYLocation = strict_cast<int>(this->mapper.at(pageIndex).row);
return pageYLocation * XOURNAL_PADDING_BETWEEN + paddingAbove;
}
auto Layout::getPaddingLeftOfPage(size_t pageIndex) const -> int {
bool isPairedPages = this->mapper.isPairedPages();
const Settings* settings = this->view->getControl()->getSettings();
auto paddingBefore = XOURNAL_PADDING;
if (settings->getUnlimitedScrolling()) {
paddingBefore += ceil_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getHorizontal()));
} else if (settings->getAddHorizontalSpace()) {
paddingBefore += settings->getAddHorizontalSpaceAmountLeft();
}
auto const pageXLocation = strict_cast<int>(this->mapper.at(pageIndex).col);
// No page pairing or we haven't rendered enough pages in the row for page pairing to have an effect,
if (!isPairedPages) {
return pageXLocation * XOURNAL_PADDING_BETWEEN + XOURNAL_PADDING_BETWEEN / 2 + paddingBefore;
} else {
auto columnPadding = XOURNAL_PADDING_BETWEEN + pageXLocation * XOURNAL_PADDING_BETWEEN;
if (pageXLocation % 2 == 0) {
return columnPadding - XOURNAL_ROOM_FOR_SHADOW + paddingBefore;
} else {
return columnPadding + XOURNAL_ROOM_FOR_SHADOW + paddingBefore;
}
}
}
void Layout::scrollRelative(double x, double y) {
if (this->view->getControl()->getSettings()->isPresentationMode()) {
return;
}
gtk_adjustment_set_value(scrollHandling->getHorizontal(),
gtk_adjustment_get_value(scrollHandling->getHorizontal()) + x);
gtk_adjustment_set_value(scrollHandling->getVertical(),
gtk_adjustment_get_value(scrollHandling->getVertical()) + y);
}
void Layout::scrollAbs(double x, double y) {
if (this->view->getControl()->getSettings()->isPresentationMode()) {
return;
}
gtk_adjustment_set_value(scrollHandling->getHorizontal(), x);
gtk_adjustment_set_value(scrollHandling->getVertical(), y);
}
void Layout::ensureRectIsVisible(int x, int y, int width, int height) {
gtk_adjustment_clamp_page(scrollHandling->getHorizontal(), x - 5, x + width + 10);
gtk_adjustment_clamp_page(scrollHandling->getVertical(), y - 5, y + height + 10);
}
auto Layout::getPageViewAt(int x, int y) -> XojPageView* {
// Binary Search:
auto rit = std::lower_bound(this->rowYStart.begin(), this->rowYStart.end(), y);
auto const foundRow = size_t(std::distance(this->rowYStart.begin(), rit));
auto cit = std::lower_bound(this->colXStart.begin(), this->colXStart.end(), x);
auto const foundCol = size_t(std::distance(this->colXStart.begin(), cit));
auto optionalPage = this->mapper.at({foundCol, foundRow});
if (optionalPage && this->view->viewPages[*optionalPage]->containsPoint(x, y, false)) {
return this->view->viewPages[*optionalPage].get();
}
return nullptr;
}
auto Layout::getPageIndexAtGridMap(size_t row, size_t col) -> std::optional<size_t> {
return this->mapper.at({col, row}); // watch out.. x,y --> c,r
}
auto Layout::getMinimalHeight() const -> int {
std::lock_guard g{pc.m};
if (!pc.valid) {
recalculate_int();
}
return strict_cast<int>(this->pc.minHeight);
}
auto Layout::getMinimalWidth() const -> int {
std::lock_guard g{pc.m};
if (!pc.valid) {
recalculate_int();
}
return strict_cast<int>(this->pc.minWidth);
}
-175
View File
@@ -1,175 +0,0 @@
/*
* Xournal++
*
* Handles the layout of the pages within a Xournal document
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <cstddef> // for size_t
#include <mutex> // for mutex
#include <optional> // for optional
#include <vector> // for vector
#include <gtk/gtk.h> // for GtkAdjustment
#include "util/Rectangle.h" // for Rectangle
#include "LayoutMapper.h" // for LayoutMapper
class XojPageView;
class XournalView;
class ScrollHandling;
/**
* @brief The Layout manager for the XournalWidget
*
* This class manages the layout of the XojPageView's contained
* in the XournalWidget
*/
class Layout final {
public:
Layout(XournalView* view, ScrollHandling* scrollHandling);
struct PreCalculated {
std::mutex m;
// The width and height of all our pages
size_t minWidth = 0;
size_t minHeight = 0;
std::vector<double> widthCols;
std::vector<double> heightRows;
bool valid = false;
};
public:
// Todo(Fabian): move to ScrollHandling also it must not depend on Layout
/**
* Increases the adjustments by the given amounts
*/
void scrollRelative(double x, double y);
// Todo(Fabian): move to ScrollHandling also it must not depend on Layout
/**
* Changes the adjustments by absolute amounts (for pinch-to-zoom)
*/
void scrollAbs(double x, double y);
// Todo(Fabian): move to XournalView
/**
* Changes the adjustments in such a way as to make sure that
* the given Rectangle is visible
*
* @remark If the given Rectangle won't fit into the scrolled window
* then only its top left corner will be visible
*/
void ensureRectIsVisible(int x, int y, int width, int height);
/**
* Returns the height of the entire Layout
*/
int getMinimalHeight() const;
/**
* Returns the width of the entire Layout
*/
int getMinimalWidth() const;
// Todo(Fabian): move to XournalView this must not depend on Layout directly
/**
* Returns the Rectangle which is currently visible
*/
xoj::util::Rectangle<double> getVisibleRect();
/**
* recalculate and resize Layout
*/
void recalculate();
/**
* Performs a layout of the XojPageView's managed in this Layout
* Sets out pages in a grid.
* Document pages are assigned to grid positions by the mapper object and may be ordered in a myriad of ways.
* ONLY call this on size allocation
*/
void layoutPages(int width, int height);
// Todo(Fabian): move to View:
/**
* Updates the current XojPageView. The XojPageView is selected based on
* the percentage of the visible area of the XojPageView relative
* to its total area.
*/
void updateVisibility();
/**
* Return the pageview containing co-ordinates.
*/
XojPageView* getPageViewAt(int x, int y);
/**
* Return the page index found ( or std::nullopt if not found) at layout grid row,col
*
*/
std::optional<size_t> getPageIndexAtGridMap(size_t row, size_t col);
/**
* @brief Get the total padding above the page at the given index.
*
* The size of this padding does not scale with pages as the user zooms in and out.
* As such, it is often necessary to get _just_ this padding.
*
* @param pageIndex is the index of the XojPageView as returned by [getIndexAtGridMap]
* @return the sum of the padding between pages above the page with the given index
* and any padding the user added vertically above all pages (i.e. in settings).
*/
int getPaddingAbovePage(size_t pageIndex) const;
/**
* @brief Get the static padding added to the left of the current page.
*
* This does not include padding added to center the page on the screen.
*
* @param pageIndex is the index of the XojPageView, as given by [getIndexAtGridMap]
* @return the sum of the padding between pages left of the page at [pageIndex] and
* any horizontal padding the user decided to add (from settings)
*/
int getPaddingLeftOfPage(size_t pageIndex) const;
protected:
// Todo(Fabian): move to ScrollHandling also it must not depend on Layout
static void horizontalScrollChanged(GtkAdjustment* adjustment, Layout* layout);
static void verticalScrollChanged(GtkAdjustment* adjustment, Layout* layout);
private:
void recalculate_int() const;
void maybeAddLastPage(Layout* layout);
// Todo(Fabian): move to ScrollHandling also it must not depend on Layout
static void checkScroll(GtkAdjustment* adjustment, double& lastScroll);
private:
XournalView* view = nullptr;
ScrollHandling* scrollHandling = nullptr;
// Todo(Fabian): move to ScrollHandling also it must not depend on Layout
double lastScrollHorizontal = -1;
double lastScrollVertical = -1;
/**
* layoutPages invalidates the precalculation of recalculate
* this bool prevents that layotPages can be called without a previously call to recalculate
* Todo: we may want to remove the additional calculation in layoutPages, since we stored those values in
*/
mutable LayoutMapper mapper;
mutable PreCalculated pc{};
mutable std::vector<unsigned> colXStart;
mutable std::vector<unsigned> rowYStart;
};
@@ -1,177 +0,0 @@
#include "LayoutMapper.h"
#include <algorithm> // for max
#include <map> // for __alloc_traits<>::value_type
#include "control/settings/Settings.h" // for Settings
constexpr auto calc_default_grid_pos = [](size_t page_nr, GridPosition grid_size,
LayoutSettings::Orientation orientation) {
return orientation == LayoutSettings::Orientation::Vertical ?
GridPosition{page_nr / grid_size.row, page_nr % grid_size.row} :
GridPosition{page_nr % grid_size.col, page_nr / grid_size.col};
};
constexpr auto map1 = [](LayoutSettings const& data, size_t page_nr) {
GridPosition grid_pos{};
auto new_nr = page_nr + static_cast<size_t>(data.offset);
if (data.showPairedPages) {
auto grid_size = GridPosition{data.cols / 2, data.rows};
grid_pos = calc_default_grid_pos(new_nr / 2, grid_size, data.orientation);
(grid_pos.col *= 2) += (new_nr % 2);
} else {
grid_pos = calc_default_grid_pos(new_nr, {data.cols, data.rows}, data.orientation);
}
decltype(grid_pos) grid_pos2 = {
(data.horizontalDir == LayoutSettings::HorizontalDirection::RightToLeft ? data.cols - 1 - grid_pos.col :
grid_pos.col),
(data.verticalDir == LayoutSettings::VerticalDirection::BottomToTop ? data.rows - 1 - grid_pos.row :
grid_pos.row)};
return grid_pos2;
};
constexpr auto map2 = [](LayoutSettings const& data, GridPosition pos) -> std::optional<size_t> {
auto isRightToLeft = [&]() { return data.horizontalDir == LayoutSettings::RightToLeft; };
auto isBottomToTop = [&]() { return data.verticalDir == LayoutSettings::BottomToTop; };
auto isVertical = [&]() { return data.orientation == LayoutSettings::Vertical; };
if (isRightToLeft()) { // reverse x
pos.col = data.cols - 1 - pos.col;
}
if (isBottomToTop()) { // reverse y
pos.row = data.rows - 1 - pos.row;
}
size_t res{};
if (isVertical()) {
if (data.showPairedPages) {
res = ((pos.row + data.rows * (pos.col / 2)) * 2) + pos.col % 2;
} else {
res = pos.row + data.rows * pos.col;
}
} else { // Horizontal
res = pos.col + data.cols * pos.row;
}
res -= data.offset;
if (res >= data.actualPages) {
return {};
}
return res;
};
// Testers
constexpr LayoutSettings my_data0{1, 91, 92, 1};
constexpr LayoutSettings my_data1{91, 1, 90, 1};
constexpr LayoutSettings my_data2{
6, 6, 35, 1, true, LayoutSettings::Vertical, LayoutSettings::RightToLeft, LayoutSettings::BottomToTop};
constexpr GridPosition test_pos0{0, 3};
constexpr GridPosition test_pos1{3, 0};
constexpr GridPosition test_pos2{3, 3};
static_assert(map1(my_data0, *map2(my_data0, test_pos0)) == test_pos0);
static_assert(map1(my_data1, *map2(my_data1, test_pos1)) == test_pos1);
static_assert(map1(my_data2, *map2(my_data2, test_pos2)) == test_pos2);
template <typename T>
constexpr auto hash(T&& data_, GridPosition const& pos) {
return pos.col + pos.row * data_.cols;
}
/**
* calculate
*
* Set mapper to LayoutType with number of pages and of fixed rows or columns
* @param data the data to be configured
* @param numRows Number of rows ( used if useRows )
* @param numCols Number of columns ( used if !useRows )
* @param useRows use pages/rows to recalculate cols else recalculate rows
* @param firstPageOffset Pages to offset - usually one or zero in order to pair up properly
*/
void calculate(LayoutSettings& data, size_t numRows, size_t numCols, bool useRows, uint32_t firstPageOffset) {
if (useRows) {
data.rows = std::max<size_t>(1UL, numRows);
// using + ( rows-1) to round up (int)pages/rows
data.cols = std::max<size_t>(1UL, (data.actualPages + firstPageOffset + (data.rows - 1)) / data.rows);
if (data.showPairedPages) {
data.cols += data.cols % 2; // make even
}
} else {
data.cols = std::max<size_t>(1UL, numCols);
if (data.showPairedPages) {
data.cols += data.cols % 2; // make even
}
data.rows = std::max<size_t>(1UL, (data.actualPages + firstPageOffset + (data.cols - 1)) / data.cols);
}
if (data.orientation == LayoutSettings::Vertical) {
// Vertical Layout
if (data.showPairedPages) {
data.offset = firstPageOffset % static_cast<uint32_t>(2 * data.rows);
} else {
data.offset = firstPageOffset % static_cast<uint32_t>(data.rows);
}
} else {
// Horizontal Layout
data.offset = firstPageOffset % static_cast<uint32_t>(data.cols);
}
}
void LayoutMapper::configureFromSettings(size_t numPages, Settings* settings) {
LayoutSettings data;
// get from user settings:
data.actualPages = numPages;
data.showPairedPages = settings->isShowPairedPages();
auto const& pairsOffset = data.showPairedPages ? static_cast<uint32_t>(settings->getPairsOffset()) : 0U;
auto const& isPresentationMode = settings->isPresentationMode();
auto const& fixRows = !isPresentationMode && settings->isViewFixedRows();
auto const& numCols = size_t(isPresentationMode ? 1 : settings->getViewColumns());
auto const& numRows = size_t(isPresentationMode ? 1 : settings->getViewRows());
// assemble bitflags for LayoutType
data.orientation = (isPresentationMode || settings->getViewLayoutVert()) ? LayoutSettings::Vertical :
LayoutSettings::Horizontal;
data.horizontalDir = settings->getViewLayoutR2L() ? LayoutSettings::RightToLeft : LayoutSettings::LeftToRight;
data.verticalDir = settings->getViewLayoutB2T() ? LayoutSettings::BottomToTop : LayoutSettings::TopToBottom;
calculate(data, numRows, numCols, fixRows, pairsOffset);
if (data == data_) {
return;
}
data_ = data;
precalculateMappers();
}
void LayoutMapper::precalculateMappers() {
this->pageToRaster.clear();
this->rasterToPage.clear();
this->pageToRaster.resize(data_.actualPages);
this->rasterToPage.resize(data_.rows * data_.cols, size_t(-1));
for (size_t i = 0; i != data_.actualPages; ++i) {
auto&& val = map1(data_, i);
this->pageToRaster[i] = val;
this->rasterToPage[hash(data_, val)] = i;
}
}
auto LayoutMapper::getColumns() const -> size_t { return data_.cols; }
auto LayoutMapper::getRows() const -> size_t { return data_.rows; }
auto LayoutMapper::isPairedPages() const -> bool { return data_.showPairedPages; }
auto LayoutMapper::at(size_t page) const -> GridPosition { return pageToRaster.at(page); }
auto LayoutMapper::at(GridPosition const& rasterXY) const -> std::optional<size_t> {
auto index = hash(data_, rasterXY);
if (index >= rasterToPage.size()) {
return std::nullopt;
}
auto val = rasterToPage[index];
return val >= data_.actualPages ? std::nullopt : std::optional<size_t>(val); // Maybe optimize this
}
@@ -1,114 +0,0 @@
/*
* Xournal++
*
* A layout manager - map where( row,column) to which page( document index)
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <cstddef> // for size_t
#include <cstdint> // for uint32_t
#include <optional> // for optional
#include <tuple> // for operator==, tie, tuple
#include <vector> // for vector
class Settings;
struct GridPosition {
size_t col{};
size_t row{};
constexpr friend bool operator==(GridPosition const& lhs, GridPosition const& rhs) noexcept {
return lhs.col == rhs.col && lhs.row == rhs.row;
}
};
struct LayoutSettings {
/// @brief The Layout of the pages
enum Orientation : bool {
Horizontal = false,
Vertical = true,
};
/// Horizontal read direction
enum HorizontalDirection : bool {
LeftToRight = false,
RightToLeft = true,
};
/// Vertical read direction
enum VerticalDirection : bool {
TopToBottom = false,
BottomToTop = true,
};
size_t cols = 0;
size_t rows = 0;
size_t actualPages = 0;
uint32_t offset = 0;
bool showPairedPages = false;
Orientation orientation = Vertical;
HorizontalDirection horizontalDir = LeftToRight;
VerticalDirection verticalDir = TopToBottom;
constexpr friend auto operator==(LayoutSettings const& lhs, LayoutSettings const& rhs) {
auto tier = [](auto&& other) {
return std::tie(other.cols, other.rows, other.actualPages, other.offset, other.showPairedPages,
other.orientation, other.horizontalDir, other.verticalDir);
};
return tier(lhs) == tier(rhs);
}
};
auto layoutSettings(Settings const*) -> LayoutSettings;
/**
* @brief Layout asks this mapper what page ( if any ) should be at a given column,row.
*/
class LayoutMapper {
public:
LayoutMapper() = default;
/**
* configureFromSettings
* Obtain user settings to determine arguments to configure().
*
* @param pages The number of pages in the document
* @param settings The Settings from which users settings are obtained
*/
void configureFromSettings(size_t numPages, Settings* settings);
auto at(size_t) const -> GridPosition;
auto at(GridPosition const&) const -> std::optional<size_t>;
size_t getColumns() const;
size_t getRows() const;
bool isPairedPages() const;
private:
void precalculateMappers();
/**
* Map page location to document index
*
* @param x Row we are interested in
* @param y Column we are interested in
*
* @return Page index to put at coordinates
*/
public:
LayoutSettings data_;
friend void calculate(LayoutSettings& data, size_t numRows, size_t numCols, bool useRows, uint32_t firstPageOffset);
std::vector<GridPosition> pageToRaster;
std::vector<size_t> rasterToPage; // access with x*y_max+y+
};
@@ -1,18 +0,0 @@
#include "LegacyRedrawable.h"
#include "model/Element.h" // for Element
#include "util/Range.h" // for Range
void LegacyRedrawable::repaintElement(Element* e) const {
repaintArea(e->getX(), e->getY(), e->getElementWidth() + e->getX(), e->getElementHeight() + e->getY());
}
void LegacyRedrawable::repaintRect(double x, double y, double width, double height) const {
repaintArea(x, y, x + width, y + height);
}
void LegacyRedrawable::rerenderRange(const Range& r) { rerenderRect(r.getX(), r.getY(), r.getWidth(), r.getHeight()); }
void LegacyRedrawable::rerenderElement(const Element* e) {
rerenderRect(e->getX(), e->getY(), e->getElementWidth(), e->getElementHeight());
}
@@ -1,87 +0,0 @@
/*
* Xournal++
*
* Interface for GUI handling
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <gdk/gdk.h> // for GdkRGBA
class Element;
class Range;
/*
* The (base) class Redrawable (now LegacyRedrawable) is deprecated. Use xoj::view::Repaintable instead.
*
* Reasons for the deprecation:
*
* * It is basically used synonymously to XojPageView.
* * More importantly, the coexistence of Redrawable::repaint... and Redrawable::rerender... is bothersome.
* it is not clear (without diving in the code) what the difference should be.
* (repaint = ask gtk to call us back to blitt the buffer, rerender = change the buffer's content)
* The rerender... methods should not exist. The derived class should be told what happened (something changed),
* and decide by itself if the buffer should be updated or not (if there is a buffer to begin with).
*/
class [[deprecated]] LegacyRedrawable {
protected:
LegacyRedrawable() = default;
public:
LegacyRedrawable(LegacyRedrawable&&) = default;
LegacyRedrawable(LegacyRedrawable const&) = default;
virtual ~LegacyRedrawable() = default;
LegacyRedrawable& operator=(LegacyRedrawable&&) = default;
LegacyRedrawable& operator=(LegacyRedrawable const&) = default;
/**
* Call this if you only need to repaint the view, this means the buffer will be painted again,
* and all selections, text editors etc. are drawn again, but the view buffer is not refreshed.
*
* for refreshing the view buffer (if you have changed the document) call rerender.
*/
virtual void repaintArea(double x1, double y1, double x2, double y2) const = 0;
[[maybe_unused]] void repaintRect(double x, double y, double width, double height) const;
[[maybe_unused]] void repaintElement(Element* e) const;
/**
* Call this if you only need to redraw the view, this means the buffer will be painted again,
* and all selections, text editors etc. are drawn again, but the view buffer is not refreshed.
*
* for refreshing the view buffer (if you have changed the document) call rerender.
*/
virtual void repaintPage() const = 0;
/**
* Rerender our buffer, then redraw the widget
* @param sizeChanged whether or not this rerendering is caused by the size of the page changing
*/
virtual void rerenderPage(bool sizeChanged = false) = 0;
/**
* Call this if you add an element, remove an element etc.
*/
void rerenderElement(const Element* e);
void rerenderRange(const Range& r);
/**
* This updated the view buffer and then rerender the the region, call this if you changed the document
*/
virtual void rerenderRect(double x, double y, double width, double height) = 0;
/**
* Return the GTK selection color
*/
virtual GdkRGBA getSelectionColor() = 0;
virtual void deleteViewBuffer() = 0;
virtual int getX() const = 0;
virtual int getY() const = 0;
};
@@ -1,742 +0,0 @@
#include "MainWindow.h"
#include <regex>
#include <gdk-pixbuf/gdk-pixbuf.h> // for gdk_pixbuf_new_fr...
#include <gdk/gdk.h> // for gdk_screen_get_de...
#include <gio/gio.h> // for g_cancellable_is_...
#include <gtk/gtkcssprovider.h> // for gtk_css_provider_...
#include "control/AudioController.h" // for AudioController
#include "control/Control.h" // for Control
#include "control/DeviceListHelper.h" // for getSourceMapping
#include "control/ScrollHandler.h" // for ScrollHandler
#include "control/actions/ActionDatabase.h" // for ActionDatabase
#include "control/jobs/XournalScheduler.h" // for XournalScheduler
#include "control/layer/LayerController.h" // for LayerController
#include "control/settings/Settings.h" // for Settings
#include "control/settings/SettingsEnums.h" // for SCROLLBAR_HIDE_HO...
#include "control/zoom/ZoomControl.h" // for ZoomControl
#include "gui/FloatingToolbox.h" // for FloatingToolbox
#include "gui/GladeGui.h" // for GladeGui
#include "gui/PdfFloatingToolbox.h" // for PdfFloatingToolbox
#include "gui/SearchBar.h" // for SearchBar
#include "gui/inputdevices/InputEvents.h" // for INPUT_DEVICE_TOUC...
#include "gui/menus/menubar/Menubar.h" // for Menubar
#include "gui/menus/menubar/ToolbarSelectionSubmenu.h" // for ToolbarSelectionSubmenu
#include "gui/scroll/ScrollHandling.h" // for ScrollHandling
#include "gui/sidebar/Sidebar.h" // for Sidebar
#include "gui/toolbarMenubar/ToolMenuHandler.h" // for ToolMenuHandler
#include "gui/toolbarMenubar/model/ToolbarData.h" // for ToolbarData
#include "gui/toolbarMenubar/model/ToolbarModel.h" // for ToolbarModel
#include "gui/widgets/SpinPageAdapter.h" // for SpinPageAdapter
#include "gui/widgets/XournalWidget.h" // for gtk_xournal_get_l...
#include "util/GListView.h" // for GListView, GListV...
#include "util/GtkUtil.h" // for getWidgetDPI
#include "util/PathUtil.h" // for getConfigFile
#include "util/StringUtils.h" // for char_cast
#include "util/Util.h" // for execInUiThread, npos
#include "util/XojMsgBox.h" // for XojMsgBox
#include "util/glib_casts.h" // for wrap_for_once_v
#include "util/gtk4_helper.h" // for gtk_widget_get_width
#include "util/i18n.h" // for FS, _F
#include "util/raii/CStringWrapper.h" // for OwnedCString
#include "GladeSearchpath.h" // for GladeSearchpath
#include "ToolbarDefinitions.h" // for TOOLBAR_DEFINITIO...
#include "XournalView.h" // for XournalView
#include "config-dev.h" // for TOOLBAR_CONFIG
#include "filesystem.h" // for path, exists
#ifdef __APPLE__
// the following header file contains a definition of struct Point that conflicts with model/Point.h
#define Point Point_CF
#include <CoreFoundation/CoreFoundation.h>
#undef Point
#endif
using std::string;
static void themeCallback(GObject*, GParamSpec*, gpointer data) { static_cast<MainWindow*>(data)->updateColorscheme(); }
MainWindow::MainWindow(GladeSearchpath* gladeSearchPath, Control* control, GtkApplication* parent):
GladeGui(gladeSearchPath, "main.glade", "mainWindow"),
control(control),
toolbar(std::make_unique<ToolMenuHandler>(control, this)),
menubar(std::make_unique<Menubar>()) {
gtk_window_set_application(GTK_WINDOW(getWindow()), parent);
panedContainerWidget.reset(get("panelMainContents"), xoj::util::ref);
boxContainerWidget.reset(get("mainContentContainer"), xoj::util::ref);
mainContentWidget.reset(get("boxContents"), xoj::util::ref);
sidebarWidget.reset(get("sidebar"), xoj::util::ref);
loadMainCSS(gladeSearchPath, "xournalpp.css");
GtkOverlay* overlay = GTK_OVERLAY(get("mainOverlay"));
this->pdfFloatingToolBox = std::make_unique<PdfFloatingToolbox>(this, overlay);
this->floatingToolbox = std::make_unique<FloatingToolbox>(this, overlay);
for (size_t i = 0; i < TOOLBAR_DEFINITIONS_LEN; i++) {
this->toolbarWidgets[i].reset(get(TOOLBAR_DEFINITIONS[i].guiName), xoj::util::ref);
}
initXournalWidget();
setSidebarVisible(control->getSettings()->isSidebarVisible());
// Window handler
g_signal_connect(this->window, "delete-event", xoj::util::wrap_for_g_callback_v<deleteEventCallback>,
this->control);
#if GTK_MAJOR_VERSION == 3
g_signal_connect(this->window, "notify::is-maximized", xoj::util::wrap_for_g_callback_v<windowMaximizedCallback>,
this);
#else
g_signal_connect(this->window, "notify::maximized", xoj::util::wrap_for_g_callback_v<windowMaximizedCallback>,
this);
#endif
// "watch over" all key events
auto keyPropagate = +[](GtkWidget* w, GdkEvent* e, gpointer) {
return gtk_window_propagate_key_event(GTK_WINDOW(w), (GdkEventKey*)(e));
};
g_signal_connect(this->window, "key-press-event", G_CALLBACK(keyPropagate), nullptr);
g_signal_connect(this->window, "key-release-event", G_CALLBACK(keyPropagate), nullptr);
updateScrollbarSidebarPosition();
gtk_window_set_default_size(GTK_WINDOW(this->window), control->getSettings()->getMainWndWidth(),
control->getSettings()->getMainWndHeight());
if (control->getSettings()->isMainWndMaximized()) {
gtk_window_maximize(GTK_WINDOW(this->window));
} else {
gtk_window_unmaximize(GTK_WINDOW(this->window));
}
Util::execInUiThread([=]() {
// Execute after the window is visible, else the check won't work
control->setShowMenubar(control->getSettings()->isMenubarVisible());
});
// Drag and Drop
g_signal_connect(this->window, "drag-data-received", G_CALLBACK(dragDataRecived), this);
gtk_drag_dest_set(this->window, GTK_DEST_DEFAULT_ALL, nullptr, 0, GDK_ACTION_COPY);
gtk_drag_dest_add_uri_targets(this->window);
gtk_drag_dest_add_image_targets(this->window);
gtk_drag_dest_add_text_targets(this->window);
g_signal_connect(gtk_widget_get_settings(this->window), "notify::gtk-theme-name", G_CALLBACK(themeCallback), this);
g_signal_connect(gtk_widget_get_settings(this->window), "notify::gtk-application-prefer-dark-theme",
G_CALLBACK(themeCallback), this);
updateColorscheme();
}
void MainWindow::populate(GladeSearchpath* gladeSearchPath) {
toolbar->populate(gladeSearchPath);
menubar->populate(gladeSearchPath, this);
// need to create tool buttons registered in plugins, so they can be added to toolbars
control->registerPluginToolButtons(this->toolbar.get());
createToolbar();
setToolbarVisible(control->getSettings()->isToolbarVisible());
}
GMenuModel* MainWindow::getMenuModel() const { return menubar->getModel(); }
MainWindow::~MainWindow() = default;
struct ThemeProperties {
bool dark;
bool darkSuffix;
std::string rootname; ///< Name without any putative -dark suffix
};
static ThemeProperties getThemeProperties(GtkWidget* w) {
xoj::util::OwnedCString name;
[[maybe_unused]] bool useEnv = false;
// Gtk prioritizes GTK_THEME over GtkSettings content
// cf https://gitlab.gnome.org/GNOME/gtk/blob/90d84a2af8b367bd5a5312b3fa3b67563462c0ef/gtk/gtksettings.c#L1567-L1622
if (auto* p = g_getenv("GTK_THEME")) {
*(name.contentReplacer()) = g_strdup(p);
useEnv = true;
} else {
g_object_get(gtk_widget_get_settings(w), "gtk-theme-name", name.contentReplacer(), nullptr);
}
// Try to figure out if the theme is dark or light
// Some themes handle their dark variant via "gtk-application-prefer-dark-theme" while other just append "-dark"
const std::regex nameparser("([a-zA-Z-]+?)([:-][dD]ark)?");
std::cmatch sm;
std::regex_match(name.get(), sm, nameparser);
ThemeProperties props;
if (sm.size() < 3) {
g_warning("Fails to extract theme root name from: \"%s\"", name.get());
props.rootname = name.get();
props.darkSuffix = false;
} else {
props.rootname = sm[1];
props.darkSuffix = sm[2].length() > 0;
}
gboolean dark = false;
#ifdef __APPLE__
if (!useEnv) {
CFStringRef interfaceStyle =
(CFStringRef)CFPreferencesCopyAppValue(CFSTR("AppleInterfaceStyle"), kCFPreferencesCurrentApplication);
if (interfaceStyle) {
char buffer[128];
if (CFStringGetCString(interfaceStyle, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
std::string style = buffer;
if (auto pos = style.find("Dark"); pos != std::string::npos) {
dark = true;
}
}
}
}
#else
g_object_get(gtk_widget_get_settings(w), "gtk-application-prefer-dark-theme", &dark, nullptr);
#endif
g_debug("Extracted theme info: Name = %s, rootname = %s, dark = %s", name.get(), props.rootname.c_str(),
dark ? "true" : "false");
props.dark = props.darkSuffix || dark; // Some themes handle their dark variant via this setting
return props;
}
void MainWindow::updateColorscheme() {
g_signal_handlers_block_by_func(gtk_widget_get_settings(this->window),
reinterpret_cast<gpointer>(G_CALLBACK(themeCallback)), this);
auto variant = control->getSettings()->getThemeVariant();
if (variant == THEME_VARIANT_USE_SYSTEM) {
gtk_settings_reset_property(gtk_widget_get_settings(this->window), "gtk-application-prefer-dark-theme");
if (modifiedGtkSettingsTheme) {
// Some bug in Gtk makes an infinite loop despite us blocking the signals
gtk_settings_reset_property(gtk_widget_get_settings(this->window), "gtk-theme-name");
modifiedGtkSettingsTheme = false;
}
}
auto props = getThemeProperties(this->window);
this->darkMode = (props.dark && variant != THEME_VARIANT_FORCE_LIGHT) || variant == THEME_VARIANT_FORCE_DARK;
// Set up icons
{
const auto uiPath = this->getGladeSearchPath()->getFirstSearchPath();
const auto lightColorIcons = (uiPath / "iconsColor-light");
const auto darkColorIcons = (uiPath / "iconsColor-dark");
const auto lightLucideIcons = (uiPath / "iconsLucide-light");
const auto darkLucideIcons = (uiPath / "iconsLucide-dark");
// icon load order from lowest priority to highest priority
std::vector<fs::path> iconLoadOrder = {};
const auto chosenTheme = control->getSettings()->getIconTheme();
switch (chosenTheme) {
case ICON_THEME_COLOR:
iconLoadOrder = {darkLucideIcons, lightLucideIcons, darkColorIcons, lightColorIcons};
break;
case ICON_THEME_LUCIDE:
iconLoadOrder = {darkColorIcons, lightColorIcons, darkLucideIcons, lightLucideIcons};
break;
default:
g_message("Unknown icon theme!");
}
if (this->darkMode) {
for (size_t i = 0; 2 * i + 1 < iconLoadOrder.size(); ++i) {
std::swap(iconLoadOrder[2 * i], iconLoadOrder[2 * i + 1]);
}
}
for (auto& p: iconLoadOrder) {
gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), char_cast(p.u8string().c_str()));
}
}
GtkStyleContext* context = gtk_widget_get_style_context(GTK_WIDGET(this->window));
if (this->darkMode) {
gtk_style_context_add_class(context, "darkMode");
g_object_set(gtk_widget_get_settings(this->window), "gtk-application-prefer-dark-theme", true, nullptr);
} else {
gtk_style_context_remove_class(context, "darkMode");
g_object_set(gtk_widget_get_settings(this->window), "gtk-application-prefer-dark-theme", false, nullptr);
if (props.darkSuffix) { // The active theme is all dark. Remove the trailing "-dark"
g_object_set(gtk_widget_get_settings(this->window), "gtk-theme-name", props.rootname.c_str(), nullptr);
modifiedGtkSettingsTheme = true;
}
}
{
gchar* name = nullptr;
g_object_get(gtk_widget_get_settings(this->window), "gtk-theme-name", &name, nullptr);
g_debug("Theme name: %s", name);
g_debug("Modified in GtkSettings: %s", modifiedGtkSettingsTheme ? "true" : "false");
g_free(name);
gboolean gtkdark = true;
g_object_get(gtk_widget_get_settings(this->window), "gtk-application-prefer-dark-theme", &gtkdark, nullptr);
g_debug("Theme variant: %s", gtkdark ? "dark" : "light");
g_debug("Icon theme: %s", iconThemeToString(control->getSettings()->getIconTheme()));
}
g_signal_handlers_unblock_by_func(gtk_widget_get_settings(this->window), reinterpret_cast<gpointer>(themeCallback),
this);
}
void MainWindow::initXournalWidget() {
winXournal = gtk_scrolled_window_new();
setGtkTouchscreenScrollingForDeviceMapping();
gtk_box_append(GTK_BOX(get("boxContents")), winXournal);
GtkWidget* vpXournal = gtk_viewport_new(nullptr, nullptr);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(winXournal), vpXournal);
scrollHandling = std::make_unique<ScrollHandling>(GTK_SCROLLED_WINDOW(winXournal));
this->xournal = std::make_unique<XournalView>(vpXournal, control, scrollHandling.get());
control->getZoomControl()->initZoomHandler(this->window, winXournal, xournal.get(), control);
gtk_widget_show_all(winXournal);
scrollHandling->init(this->xournal->getWidget(), this->xournal->getLayout());
}
void MainWindow::setGtkTouchscreenScrollingForDeviceMapping() {
InputDeviceClass touchscreenClass =
DeviceListHelper::getSourceMapping(GDK_SOURCE_TOUCHSCREEN, this->getControl()->getSettings());
setGtkTouchscreenScrollingEnabled(touchscreenClass == INPUT_DEVICE_TOUCHSCREEN &&
!control->getSettings()->getTouchDrawingEnabled());
}
void MainWindow::setGtkTouchscreenScrollingEnabled(bool enabled) {
if (!control->getSettings()->getGtkTouchInertialScrollingEnabled()) {
enabled = false;
}
gtk_scrolled_window_set_kinetic_scrolling(GTK_SCROLLED_WINDOW(winXournal), enabled);
}
auto MainWindow::getLayout() const -> Layout* { return this->xournal->getLayout(); }
auto MainWindow::getNegativeXournalWidgetPos() const -> xoj::util::Point<double> {
return Util::toWidgetCoords(this->winXournal, xoj::util::Point{0.0, 0.0});
}
auto cancellable_cancel(GCancellable* cancel) -> bool {
g_cancellable_cancel(cancel);
g_warning("Timeout... Cancel loading URL");
return false;
}
void MainWindow::dragDataRecived(GtkWidget* widget, GdkDragContext* dragContext, gint x, gint y, GtkSelectionData* data,
guint info, guint time, MainWindow* win) {
GtkWidget* source = gtk_drag_get_source_widget(dragContext);
if (source && widget == gtk_widget_get_toplevel(source)) {
gtk_drag_finish(dragContext, false, false, time);
return;
}
guchar* text = gtk_selection_data_get_text(data);
if (text) {
win->control->clipboardPasteText(reinterpret_cast<const char*>(text));
g_free(text);
gtk_drag_finish(dragContext, true, false, time);
return;
}
xoj::util::GObjectSPtr<GdkPixbuf> image(gtk_selection_data_get_pixbuf(data), xoj::util::adopt);
if (image) {
win->control->clipboardPasteImage(image.get());
gtk_drag_finish(dragContext, true, false, time);
return;
}
gchar** uris = gtk_selection_data_get_uris(data);
if (uris) {
for (int i = 0; uris[i] != nullptr && i < 3; i++) {
const char* uri = uris[i];
GCancellable* cancel = g_cancellable_new();
auto cancelTimeout = g_timeout_add(3000, xoj::util::wrap_for_once_v<cancellable_cancel>, cancel);
xoj::util::GObjectSPtr<GFile> file(g_file_new_for_uri(uri), xoj::util::adopt);
GError* err = nullptr;
GFileInputStream* in = g_file_read(file.get(), cancel, &err);
if (g_cancellable_is_cancelled(cancel)) {
continue;
}
if (err == nullptr) {
xoj::util::GObjectSPtr<GdkPixbuf> pixbuf(
gdk_pixbuf_new_from_stream(G_INPUT_STREAM(in), cancel, nullptr), xoj::util::adopt);
if (g_cancellable_is_cancelled(cancel)) {
continue;
}
g_input_stream_close(G_INPUT_STREAM(in), cancel, nullptr);
if (g_cancellable_is_cancelled(cancel)) {
continue;
}
if (pixbuf) {
win->control->clipboardPasteImage(pixbuf.get());
}
} else {
g_error_free(err);
}
if (!g_cancellable_is_cancelled(cancel)) {
g_source_remove(cancelTimeout);
}
g_object_unref(cancel);
}
gtk_drag_finish(dragContext, true, false, time);
g_strfreev(uris);
}
gtk_drag_finish(dragContext, false, false, time);
}
auto MainWindow::getControl() const -> Control* { return control; }
void MainWindow::updateScrollbarSidebarPosition() {
// Part 1: update scrollbar position
if (winXournal != nullptr) {
GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(winXournal);
ScrollbarHideType type = this->getControl()->getSettings()->getScrollbarHideType();
bool scrollbarOnLeft = control->getSettings()->isScrollbarOnLeft();
if (scrollbarOnLeft) {
gtk_scrolled_window_set_placement(scrolledWindow, GTK_CORNER_TOP_RIGHT);
} else {
gtk_scrolled_window_set_placement(scrolledWindow, GTK_CORNER_TOP_LEFT);
}
gtk_widget_set_visible(gtk_scrolled_window_get_hscrollbar(scrolledWindow), !(type & SCROLLBAR_HIDE_HORIZONTAL));
gtk_widget_set_visible(gtk_scrolled_window_get_vscrollbar(scrolledWindow), !(type & SCROLLBAR_HIDE_VERTICAL));
gtk_scrolled_window_set_overlay_scrolling(scrolledWindow,
!control->getSettings()->isScrollbarFadeoutDisabled());
}
// Part 2: update sidebar position
GtkPaned* paned = GTK_PANED(this->panedContainerWidget.get());
// Allocation is reset when we switch up the contained elements. Fetch the
// width here in case we need it afterwards.
int contentWidth = gtk_widget_get_width(this->boxContainerWidget.get());
bool sidebarRight = control->getSettings()->isSidebarOnRight();
if (sidebarRight != (gtk_paned_get_child2(paned) == this->sidebarWidget.get())) {
// switch sidebar and main content
GtkWidget* sidebar = this->sidebarWidget.get();
GtkWidget* mainContent = this->sidebarVisible ? this->mainContentWidget.get() : nullptr;
#if GTK_MAJOR_VERSION == 3
if (this->sidebarVisible) {
gtk_container_remove(GTK_CONTAINER(paned), sidebar);
gtk_container_remove(GTK_CONTAINER(paned), mainContent);
if (sidebarRight) {
gtk_paned_pack1(paned, mainContent, true, false);
gtk_paned_pack2(paned, sidebar, false, false);
} else {
gtk_paned_pack1(paned, sidebar, false, false);
gtk_paned_pack2(paned, mainContent, true, false);
}
} else {
// The sidebar is hidden. That means the paned widget only contains the
// sidebar while the main contents are shown alone in the box container.
gtk_container_remove(GTK_CONTAINER(paned), sidebar);
if (sidebarRight) {
gtk_paned_pack2(paned, sidebar, false, false);
} else {
gtk_paned_pack1(paned, sidebar, false, false);
}
}
#else
gtk_paned_set_start_child(paned, nullptr);
gtk_paned_set_end_child(paned, nullptr);
if (sidebarRight) {
gtk_paned_set_start_child(paned, mainContent);
gtk_paned_set_resize_start_child(paned, true);
gtk_paned_set_shrink_start_child(paned, false);
gtk_paned_set_end_child(paned, sidebar);
gtk_paned_set_resize_end_child(paned, false);
gtk_paned_set_shrink_end_child(paned, false);
} else {
gtk_paned_set_end_child(paned, mainContent);
gtk_paned_set_resize_end_child(paned, true);
gtk_paned_set_shrink_end_child(paned, false);
gtk_paned_set_start_child(paned, sidebar);
gtk_paned_set_resize_start_child(paned, false);
gtk_paned_set_shrink_start_child(paned, false);
}
#endif
}
if (this->sidebarVisible) {
updatePanedPosition(contentWidth);
}
}
auto MainWindow::deleteEventCallback(GtkWidget* widget, GdkEvent* event, Control* control) -> bool {
control->quit();
return true;
}
void MainWindow::setSidebarVisible(bool visible) {
if (!visible && (this->control->getSidebar() != nullptr)) {
this->control->getSidebar()->saveSize();
}
if (visible != this->sidebarVisible) {
// Due to a GTK bug, we can't just hide the sidebar widget in the GtkPaned.
// If we do this, we create a dead region where the pane separator was previously.
// In this region, we can't use the touchscreen to start horizontal strokes.
// As such:
if (!visible) {
// hide sidebar
#if GTK_MAJOR_VERSION == 3
gtk_container_remove(GTK_CONTAINER(panedContainerWidget.get()), mainContentWidget.get());
#else
if (control->getSettings()->isSidebarOnRight()) {
gtk_paned_set_start_child(GTK_PANED(panedContainerWidget.get()), nullptr);
} else {
gtk_paned_set_end_child(GTK_PANED(panedContainerWidget.get()), nullptr);
}
#endif
gtk_box_remove(GTK_BOX(boxContainerWidget.get()), panedContainerWidget.get());
gtk_box_append(GTK_BOX(boxContainerWidget.get()), mainContentWidget.get());
this->sidebarVisible = false;
} else {
// show sidebar
// Allocation is reset when we switch up the contained elements. Fetch the
// width here in case we need it afterwards.
int contentWidth = gtk_widget_get_width(boxContainerWidget.get());
gtk_box_remove(GTK_BOX(boxContainerWidget.get()), mainContentWidget.get());
#if GTK_MAJOR_VERSION == 3
if (control->getSettings()->isSidebarOnRight()) {
gtk_paned_pack1(GTK_PANED(panedContainerWidget.get()), mainContentWidget.get(), true, false);
} else {
gtk_paned_pack2(GTK_PANED(panedContainerWidget.get()), mainContentWidget.get(), true, false);
}
#else
if (control->getSettings()->isSidebarOnRight()) {
gtk_paned_set_start_child(GTK_PANED(panedContainerWidget.get()), mainContentWidget.get());
} else {
gtk_paned_set_end_child(GTK_PANED(panedContainerWidget.get()), mainContentWidget.get());
}
#endif
gtk_box_append(GTK_BOX(boxContainerWidget.get()), panedContainerWidget.get());
this->sidebarVisible = true;
updatePanedPosition(contentWidth);
}
}
gtk_widget_set_visible(sidebarWidget.get(), visible);
}
/**
* Invert the position of the paned widget and disconnect from the signal.
* @param handlerId should be the ID of the signal handler that should be disconnected.
*/
static void invertPanedPosition(GtkWidget* widget, GtkAllocation* allocation, gulong* handlerId) {
int newDividerPos = allocation->width - gtk_paned_get_position(GTK_PANED(widget));
gtk_paned_set_position(GTK_PANED(widget), newDividerPos);
// We only need to switch the position once, so disconnect the signal right away.
g_signal_handler_disconnect(widget, *handlerId);
}
void MainWindow::updatePanedPosition(int contentWidth) {
if (!this->control->getSettings()->isSidebarOnRight()) {
// Sidebar is on the left side.
gtk_paned_set_position(GTK_PANED(this->panedContainerWidget.get()),
this->control->getSettings()->getSidebarWidth());
} else {
// Sidebar is on the right side.
if (contentWidth > 0) {
int dividerPos = contentWidth - this->control->getSettings()->getSidebarWidth();
gtk_paned_set_position(GTK_PANED(this->panedContainerWidget.get()), dividerPos);
} else {
// Allocation is unkown (window hasn't been shown yet). We have to wait for the signal.
// Set position as if the sidebar was on the left side, and let the signal handler
// simply invert the position when the allocation is known.
gtk_paned_set_position(GTK_PANED(this->panedContainerWidget.get()),
this->control->getSettings()->getSidebarWidth());
gulong* signal_id = new gulong{};
*signal_id = g_signal_connect_data(
this->panedContainerWidget.get(), "size-allocate",
xoj::util::wrap_for_g_callback_v<invertPanedPosition>, signal_id,
[](gpointer d, GClosure*) { delete reinterpret_cast<gulong*>(d); }, GConnectFlags(0));
}
}
}
void MainWindow::setToolbarVisible(bool visible) {
Settings* settings = control->getSettings();
settings->setToolbarVisible(visible);
for (auto& w: this->toolbarWidgets) {
if (!visible || (gtk_toolbar_get_n_items(GTK_TOOLBAR(w.get())) != 0)) {
gtk_widget_set_visible(w.get(), visible);
}
}
}
void MainWindow::setMenubarVisible(bool visible) {
gtk_application_window_set_show_menubar(GTK_APPLICATION_WINDOW(this->getWindow()), visible);
}
void MainWindow::setMaximized(bool maximized) { this->maximized = maximized; }
auto MainWindow::isMaximized() const -> bool { return this->maximized; }
auto MainWindow::setFullscreen(bool enabled) const -> void {
if (enabled) {
gtk_window_fullscreen(GTK_WINDOW(this->getWindow()));
} else {
gtk_window_unfullscreen(GTK_WINDOW(this->getWindow()));
}
}
auto MainWindow::isDarkTheme() const -> bool { return this->darkMode; }
auto MainWindow::getXournal() const -> XournalView* { return xournal.get(); }
auto MainWindow::windowMaximizedCallback(GObject* window, GParamSpec*, MainWindow* win) -> void {
win->setMaximized(gtk_window_is_maximized(GTK_WINDOW(window)));
}
void MainWindow::toolbarSelected(const std::string& id) {
const auto& toolbars = toolbar->getModel()->getToolbars();
auto it = std::find_if(toolbars.begin(), toolbars.end(), [&](const auto& d) { return d->getId() == id; });
toolbarSelected(it == toolbars.end() ? nullptr : it->get());
}
void MainWindow::toolbarSelected(ToolbarData* d) {
if (!d || this->selectedToolbar == d) {
return;
}
Settings* settings = control->getSettings();
settings->setSelectedToolbar(d->getId());
this->clearToolbar();
this->loadToolbar(d);
}
auto MainWindow::clearToolbar() -> const ToolbarData* {
if (this->selectedToolbar != nullptr) {
for (size_t i = 0; i < TOOLBAR_DEFINITIONS_LEN; i++) {
ToolMenuHandler::unloadToolbar(this->toolbarWidgets[i].get());
}
this->toolbar->freeDynamicToolbarItems();
}
return std::exchange(this->selectedToolbar, nullptr);
}
void MainWindow::loadToolbar(ToolbarData* d) {
this->selectedToolbar = d;
for (size_t i = 0; i < TOOLBAR_DEFINITIONS_LEN; i++) {
this->toolbar->load(d, this->toolbarWidgets[i].get(), TOOLBAR_DEFINITIONS[i].propName,
TOOLBAR_DEFINITIONS[i].horizontal);
}
this->floatingToolbox->flagRecalculateSizeRequired();
}
void MainWindow::reloadToolbars() {
ToolbarData* d = getSelectedToolbar();
this->clearToolbar();
this->toolbarSelected(d);
}
auto MainWindow::getSelectedToolbar() const -> ToolbarData* { return this->selectedToolbar; }
auto MainWindow::getToolbarWidgets() const -> const ToolbarWidgetArray& { return toolbarWidgets; }
auto MainWindow::getToolbarName(GtkToolbar* toolbar) const -> const char* {
for (size_t i = 0; i < TOOLBAR_DEFINITIONS_LEN; i++) {
if (static_cast<void*>(this->toolbarWidgets[i].get()) == static_cast<void*>(toolbar)) {
return TOOLBAR_DEFINITIONS[i].propName;
}
}
return "";
}
void MainWindow::setDynamicallyGeneratedSubmenuDisabled(bool disabled) { menubar->setDisabled(disabled); }
void MainWindow::updateToolbarMenu() {
menubar->getToolbarSelectionSubmenu().update(toolbar.get(), this->selectedToolbar);
}
void MainWindow::createToolbar() {
toolbarSelected(control->getSettings()->getSelectedToolbar());
this->control->getScheduler()->unblockRerenderZoom();
}
void MainWindow::updatePageNumbers(size_t page, size_t pagecount, size_t pdfpage) {
toolbar->setPageInfo(page, pagecount, pdfpage);
}
auto MainWindow::getMenubar() const -> Menubar* { return menubar.get(); }
void MainWindow::show(GtkWindow* parent) { gtk_widget_show(this->window); }
void MainWindow::setUndoDescription(const string& description) { menubar->setUndoDescription(description); }
void MainWindow::setRedoDescription(const string& description) { menubar->setRedoDescription(description); }
auto MainWindow::getToolbarModel() const -> ToolbarModel* { return this->toolbar->getModel(); }
auto MainWindow::getToolMenuHandler() const -> ToolMenuHandler* { return this->toolbar.get(); }
void MainWindow::loadMainCSS(GladeSearchpath* gladeSearchPath, const gchar* cssFilename) {
auto filepath = gladeSearchPath->findFile("", cssFilename);
xoj::util::GObjectSPtr<GtkCssProvider> provider(gtk_css_provider_new(), xoj::util::adopt);
gtk_css_provider_load_from_path(provider.get(), char_cast(filepath.u8string().c_str()), nullptr);
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(provider.get()),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
PdfFloatingToolbox* MainWindow::getPdfToolbox() const { return this->pdfFloatingToolBox.get(); }
FloatingToolbox* MainWindow::getFloatingToolbox() const { return this->floatingToolbox.get(); }
void MainWindow::setDPI() const {
if (auto dpi = this->getControl()->getSettings()->getDisplayDpi(); dpi == -1) {
auto res = xoj::util::gtk::getWidgetDPI(this->window);
this->getControl()->getZoomControl()->setZoom100Value(res.value_or(Util::DPI_NORMALIZATION_FACTOR) /
Util::DPI_NORMALIZATION_FACTOR);
} else {
this->getControl()->getZoomControl()->setZoom100Value(dpi / Util::DPI_NORMALIZATION_FACTOR);
}
}
@@ -1,196 +0,0 @@
/*
* Xournal++
*
* The Main window
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <array> // for array
#include <atomic> // for atomic_bool
#include <cstddef> // for size_t
#include <memory> // for unique_ptr
#include <string> // for string
#include <gdk/gdk.h> // for GdkDragContext, GdkEvent
#include <glib-object.h> // for GClosure
#include <glib.h> // for gpointer, gboolean, gint
#include <gtk/gtk.h> // for GtkWidget, GtkCheckMenu...
#include "util/Point.h"
#include "util/raii/GObjectSPtr.h"
#include "GladeGui.h" // for GladeGui
#include "ToolbarDefinitions.h" // for TOOLBAR_DEFINITIONS_LEN
class Control;
class Layout;
class SpinPageAdapter;
class ScrollHandling;
class ToolMenuHandler;
class ToolbarData;
class ToolbarModel;
class XournalView;
class PdfFloatingToolbox;
class FloatingToolbox;
class GladeSearchpath;
class Menubar;
typedef std::array<xoj::util::WidgetSPtr, TOOLBAR_DEFINITIONS_LEN> ToolbarWidgetArray;
class MainWindow: public GladeGui {
public:
MainWindow(GladeSearchpath* gladeSearchPath, Control* control, GtkApplication* parent);
~MainWindow() override;
void populate(GladeSearchpath* gladeSearchPath);
public:
GMenuModel* getMenuModel() const;
void show(GtkWindow* parent) override;
void toolbarSelected(const std::string& id);
void toolbarSelected(ToolbarData* d);
ToolbarData* getSelectedToolbar() const;
private:
const ToolbarData* clearToolbar();
void loadToolbar(ToolbarData* d);
public:
/**
* reloadToolbars reloads the currently selected toolbar
*
* This is especially useful when a change in the SettingsDialog should be reflected right away
*/
void reloadToolbars();
void updatePageNumbers(size_t page, size_t pagecount, size_t pdfpage);
void setMaximized(bool maximized);
bool isMaximized() const;
void setFullscreen(bool enabled) const;
bool isDarkTheme() const;
XournalView* getXournal() const;
void setMenubarVisible(bool visible);
void setSidebarVisible(bool visible);
void setToolbarVisible(bool visible);
Control* getControl() const;
PdfFloatingToolbox* getPdfToolbox() const;
FloatingToolbox* getFloatingToolbox() const;
void updateScrollbarSidebarPosition();
void setUndoDescription(const std::string& description);
void setRedoDescription(const std::string& description);
ToolbarModel* getToolbarModel() const;
ToolMenuHandler* getToolMenuHandler() const;
void setDynamicallyGeneratedSubmenuDisabled(bool disabled);
void updateToolbarMenu();
void updateColorscheme();
const ToolbarWidgetArray& getToolbarWidgets() const;
const char* getToolbarName(GtkToolbar* toolbar) const;
Layout* getLayout() const;
[[maybe_unused]] Menubar* getMenubar() const;
/**
* Get the position of the top left corner of screen (X11) or the window (Wayland)
* relative to the Xournal Widget top left corner
*
* @see Util::toWidgetCoords()
*/
xoj::util::Point<double> getNegativeXournalWidgetPos() const;
/**
* Disable kinetic scrolling if there is a touchscreen device that was manually mapped to another enabled input
* device class. This is required so the GtkScrolledWindow does not swallow all the events.
*/
void setGtkTouchscreenScrollingForDeviceMapping();
void setGtkTouchscreenScrollingEnabled(bool enabled);
/// Infer the window's DPI from available monitor info and use it to set the default zoom value.
void setDPI() const;
private:
void initXournalWidget();
void createToolbar();
/**
* Update the position of the separator in the paned container, adjusting it to the saved sidebar width.
* @param contentWidth should be the width of the paned container. The caller should retrieve the width
* of the container before any modifications to it, as that will reset its allocation.
*/
void updatePanedPosition(int contentWidth);
/**
* Window close Button is pressed
*/
static bool deleteEventCallback(GtkWidget* widget, GdkEvent* event, Control* control);
/**
* Window is maximized/minimized
*/
static void windowMaximizedCallback(GObject* window, GParamSpec*, MainWindow* win);
/**
* Callback for drag & drop files
*/
static void dragDataRecived(GtkWidget* widget, GdkDragContext* dragContext, gint x, gint y, GtkSelectionData* data,
guint info, guint time, MainWindow* win);
/**
* Load Overall CSS file with custom icons, other styling and potentially, user changes
*/
static void loadMainCSS(GladeSearchpath* gladeSearchPath, const gchar* cssFilename);
private:
Control* control;
std::unique_ptr<XournalView> xournal;
GtkWidget* winXournal = nullptr;
std::unique_ptr<ScrollHandling> scrollHandling;
std::atomic_bool gtkTouchscreenScrollingEnabled{true};
std::unique_ptr<PdfFloatingToolbox> pdfFloatingToolBox;
std::unique_ptr<FloatingToolbox> floatingToolbox;
// Toolbars
std::unique_ptr<ToolMenuHandler> toolbar;
ToolbarData* selectedToolbar = nullptr;
std::unique_ptr<Menubar> menubar;
bool maximized = false;
bool darkMode = false;
bool modifiedGtkSettingsTheme = false;
ToolbarWidgetArray toolbarWidgets;
bool sidebarVisible = true;
xoj::util::WidgetSPtr boxContainerWidget;
xoj::util::WidgetSPtr panedContainerWidget;
xoj::util::WidgetSPtr mainContentWidget;
xoj::util::WidgetSPtr sidebarWidget;
};
@@ -1,129 +0,0 @@
#include "PagePreviewDecoration.h"
#include <math.h>
#include "control/Control.h"
#include "control/settings/Settings.h"
#include "gui/MainWindow.h"
#include "util/raii/CairoWrappers.h"
void PagePreviewDecoration::drawDecoration(cairo_t* cr, SidebarPreviewPageEntry* pageEntry, Control* control) {
switch (control->getSettings()->getSidebarNumberingStyle()) {
case SidebarNumberingStyle::NUMBER_BELOW_PREVIEW:
PagePreviewDecoration::drawPageNumberBelowPreview(cr, pageEntry, control);
break;
case SidebarNumberingStyle::NUMBER_WITH_CIRCULAR_BACKGROUND:
PagePreviewDecoration::drawPageNumberWithCircleBackground(cr, pageEntry, control);
break;
case SidebarNumberingStyle::NUMBER_WITH_SQUARE_BACKGROUND:
PagePreviewDecoration::drawPageNumberWithSquareBackground(cr, pageEntry, control);
break;
default:
break;
}
}
void PagePreviewDecoration::drawPageNumberBelowPreview(cairo_t* cr, SidebarPreviewPageEntry* pageEntry,
Control* control) {
xoj::util::CairoSaveGuard saveGuard(cr);
cairo_text_extents_t extents;
std::string pageNumber = std::to_string(pageEntry->getIndex() + 1);
Color color = control->getWindow()->isDarkTheme() ? Colors::white : Colors::xopp_darkslategray;
if (pageEntry->isSelected()) {
color = control->getSettings()->getBorderColor();
}
Util::cairo_set_source_rgbi(cr, color);
cairo_select_font_face(cr, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, FONT_SIZE);
cairo_text_extents(cr, pageNumber.c_str(), &extents);
double x = pageEntry->getWidth() / 2 - (extents.width / 2 + extents.x_bearing);
double y = pageEntry->getHeight() - MARGIN_BOTTOM - (extents.height / 2 + extents.y_bearing);
cairo_move_to(cr, x, y);
cairo_show_text(cr, pageNumber.c_str());
}
void PagePreviewDecoration::drawPageNumberWithCircleBackground(cairo_t* cr, SidebarPreviewPageEntry* pageEntry,
Control* control) {
xoj::util::CairoSaveGuard saveGuard(cr);
cairo_text_extents_t extents;
std::string pageNumber = std::to_string(pageEntry->getIndex() + 1);
cairo_select_font_face(cr, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, FONT_SIZE);
cairo_text_extents(cr, pageNumber.c_str(), &extents);
// Set the color for drawing the circle. This circle is required to provide good contrast for the page number.
if (pageEntry->isSelected()) {
Color color = control->getSettings()->getBorderColor();
Util::cairo_set_source_rgbi(cr, color, CIRCLE_LABEL_OPACITY);
} else {
Util::cairo_set_source_rgbi(cr, Colors::xopp_darkslategray, CIRCLE_LABEL_OPACITY);
}
// How many pixels to indent the circle center from the widget border
double indentRight = pageEntry->getWidth() * CIRCLE_INDENT_RIGHT;
double indentBottom = pageEntry->getHeight() * CIRCLE_INDENT_BOTTOM;
// Pixel position of circle center
double x = pageEntry->getWidth() - indentRight;
double y = pageEntry->getHeight() - indentBottom;
// Inner circle width depends on the actual text width
double addititionalWidth = std::max((extents.width - 2 * CIRCLE_RADIUS) + CIRCLE_TEXT_PADDING, 0.0);
// Draw an slightly transparent circle in the lower right corner of the preview
cairo_set_line_width(cr, LINE_WIDTH);
cairo_arc(cr, x - addititionalWidth, y, CIRCLE_RADIUS, M_PI * 0.5, M_PI * 1.5);
cairo_arc(cr, x, y, CIRCLE_RADIUS, M_PI * 1.5, M_PI * 0.5);
cairo_close_path(cr);
cairo_stroke_preserve(cr);
cairo_fill(cr);
// Draw the page number in the center of the previously drawn circle
cairo_move_to(cr, x - addititionalWidth / 2 - (extents.width / 2 + extents.x_bearing), y + CIRCLE_TEXT_Y);
Util::cairo_set_source_argb(cr, Colors::white);
cairo_show_text(cr, pageNumber.c_str());
}
void PagePreviewDecoration::drawPageNumberWithSquareBackground(cairo_t* cr, SidebarPreviewPageEntry* pageEntry,
Control* control) {
xoj::util::CairoSaveGuard saveGuard(cr);
cairo_text_extents_t extents;
std::string pageNumber = std::to_string(pageEntry->getIndex() + 1);
cairo_select_font_face(cr, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, FONT_SIZE);
cairo_text_extents(cr, pageNumber.c_str(), &extents);
// Select the color for painting the square background
if (pageEntry->isSelected()) {
Util::cairo_set_source_rgbi(cr, control->getSettings()->getBorderColor());
} else {
Util::cairo_set_source_rgbi(cr, Colors::xopp_darkslategray);
// In case the page is not selected draw a border around the preview to match the selected
// See discussion: <https://github.com/xournalpp/xournalpp/issues/4624#issue-1557719574>
cairo_set_line_width(cr, PREVIEW_BORDER_LINE_WIDTH);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL);
cairo_rectangle(cr, PREVIEW_UPPER_LEFT, PREVIEW_UPPER_LEFT, pageEntry->getWidth() - PREVIEW_WIDTH_INDENT,
pageEntry->getHeight() - PREVIEW_HEIGHT_INDENT);
cairo_stroke(cr);
}
// Calculate the rectangle width depending on the page index number of digits
double recWidth = std::max(extents.width + 8, SQUARE_SIZE);
// Calculate the position of the upper left corner of the square
double x = pageEntry->getWidth() - SQUARE_INDENT_RIGHT - recWidth;
double y = pageEntry->getHeight() - SQUARE_INDENT_BOTTOM - SQUARE_SIZE;
// Draw an square in the lower right corner
cairo_set_line_width(cr, LINE_WIDTH);
cairo_rectangle(cr, x, y, recWidth, SQUARE_SIZE);
cairo_stroke_preserve(cr);
cairo_fill(cr);
// Draw the page number in the center of the square
cairo_move_to(cr, x + recWidth / 2 - (extents.width / 2 + extents.x_bearing), y + SQUARE_TEXT_Y);
Util::cairo_set_source_argb(cr, Colors::white);
cairo_show_text(cr, pageNumber.c_str());
}
@@ -1,70 +0,0 @@
/*
* Xournal++
*
* Settings Dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <cairo.h> // for cairo_surface_t, cairo_t
#include "sidebar/previews/page/SidebarPreviewPageEntry.h"
class Control;
class PagePreviewDecoration {
public:
static void drawDecoration(cairo_t* cr, SidebarPreviewPageEntry* pageEntry, Control* control);
private:
static void drawPageNumberBelowPreview(cairo_t* cr, SidebarPreviewPageEntry* pageEntry, Control* control);
static void drawPageNumberWithCircleBackground(cairo_t* cr, SidebarPreviewPageEntry* pageEntry, Control* control);
static void drawPageNumberWithSquareBackground(cairo_t* cr, SidebarPreviewPageEntry* pageEntry, Control* control);
public:
/* For the style with the page numbers below the preview the widget height has to be extended by this amount */
static constexpr int MARGIN_BOTTOM = 20;
private:
/* The font sized used for drawing the page number*/
static constexpr double FONT_SIZE = 16.0;
/* Line width for drawing the circle and the square background*/
static constexpr double LINE_WIDTH = 1.0;
/* Indention of the circle from the right border as percentage to the total widget width */
static constexpr double CIRCLE_INDENT_RIGHT = 0.28;
/* Indention of the circle from the bottom border as percentage to the total widget height */
static constexpr double CIRCLE_INDENT_BOTTOM = 0.2;
/* Transparency value for the circle background */
static constexpr double CIRCLE_LABEL_OPACITY = 0.6;
/* Circle radius in pixels */
static constexpr double CIRCLE_RADIUS = 11;
/* Vertical alignment of page number to circle center */
static constexpr double CIRCLE_TEXT_Y = 6;
/* Padding for the text in the circle */
static constexpr double CIRCLE_TEXT_PADDING = 8;
/* Height and minimum width of background square in pixels */
static constexpr double SQUARE_SIZE = 20;
/* Pixel-Distance form widgets right border to previews right border */
static constexpr double SQUARE_INDENT_RIGHT = 12;
/* Pixel-Distance from widgets bottom border to previews bottom border */
static constexpr double SQUARE_INDENT_BOTTOM = 12;
/* Vertical alignment of the page number in the square */
static constexpr double SQUARE_TEXT_Y = 16;
/* Padding for the text in the circle, to be applied on both sides*/
static constexpr double SQUARE_TEXT_PADDING = 4;
static constexpr double PREVIEW_BORDER_LINE_WIDTH = 2.0;
static constexpr double PREVIEW_UPPER_LEFT = 8.5;
static constexpr double PREVIEW_WIDTH_INDENT = 21;
static constexpr double PREVIEW_HEIGHT_INDENT = 21;
static constexpr char FONT_NAME[] = "Sans";
};
File diff suppressed because it is too large. Load diff
-323
View File
@@ -1,323 +0,0 @@
/*
* Xournal++
*
* Displays a single page
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <cstddef> // for size_t
#include <memory> // for unique_ptr, shared_ptr
#include <mutex> // for mutex
#include <string> // for string
#include <vector> // for vector
#include <cairo.h> // for cairo_t
#include <gdk/gdk.h> // for GdkEventKey, GdkRGBA, GdkRectangle
#include <gtk/gtk.h> // for GtkWidget
#include "gui/inputdevices/DeviceId.h"
#include "gui/inputdevices/InputEvents.h"
#include "model/PageListener.h" // for PageListener
#include "model/PageRef.h" // for PageRef
#include "util/Rectangle.h" // for Rectangle
#include "util/raii/CairoWrappers.h" // for CairoSurfaceSPtr
#include "view/Mask.h" // for Mask
#include "view/Repaintable.h" // for Repaintable
#include "Layout.h" // for Layout
#include "LegacyRedrawable.h" // for LegacyRedrawable
class EraseHandler;
class ImageSizeSelection;
class InputHandler;
class LaserPointerHandler;
class SearchControl;
class Selector;
class Settings;
class Text;
class TextEditor;
class VerticalToolHandler;
class XournalView;
class Element;
class PositionInputData;
class Range;
class TexImage;
class XojPdfRectangle;
class XojPdfPage;
namespace xoj::view {
class OverlayView;
class ToolView;
} // namespace xoj::view
class XojPageView: public LegacyRedrawable, public PageListener, public xoj::view::Repaintable {
public:
XojPageView(XournalView* xournal, const PageRef& page);
~XojPageView() override;
public:
void addOverlayView(std::unique_ptr<xoj::view::OverlayView>);
void rerenderPage(bool sizeChanged = false) override;
void rerenderRect(double x, double y, double width, double height) override;
void repaintPage() const override;
void repaintArea(double x1, double y1, double x2, double y2) const override;
// Repaintable interface
void flagDirtyRegion(const Range& rg) const override;
/**
* @brief This draws the ToolView directly onto the buffer, deletes the view and repaints the given range
* Used to avoid blinking when a tool finished editing an element
*/
void drawAndDeleteToolView(xoj::view::ToolView* v, const Range& rg) override;
/**
* @brief Simply deletes an overlay and any trace of it on the display (provided the overlay is contained in the
* given range)
*/
void deleteOverlayView(xoj::view::OverlayView* v, const Range& rg) override;
double getZoom() const override;
ZoomControl* getZoomControl() const override;
Range getVisiblePart() const override;
double getWidth() const override;
double getHeight() const override;
// End of Repaintable interface
xoj::util::Rectangle<double> toWindowCoordinates(const xoj::util::Rectangle<double>& r) const override;
void setSelected(bool selected);
void setIsVisible(bool visible);
bool isSelected() const;
inline bool isVisible() const { return visible; }
void endText();
void endSpline();
bool searchTextOnPage(const std::string& text, size_t index, size_t* occurrences, XojPdfRectangle* matchRect);
bool onKeyPressEvent(const KeyEvent& event);
bool onKeyReleaseEvent(const KeyEvent& event);
bool cut();
bool copy();
bool paste();
bool actionDelete();
void deleteViewBuffer() override;
/**
* Returns whether this PageView contains the
* given point on the display
*/
bool containsPoint(int x, int y, bool local = false) const;
/**
* Returns Row assigned in current layout
*/
int getMappedRow() const;
/**
* Returns Column assigned in current layout
*/
int getMappedCol() const;
GdkRGBA getSelectionColor() override;
bool hasBuffer() const;
TextEditor* getTextEditor();
/**
* Returns a reference to the XojPage belonging to
* this PageView
*/
const PageRef getPage() const;
XournalView* getXournal() const;
/**
* Returns the width of this XojPageView as displayed
* on the display taking into account the current zoom
*/
int getDisplayWidth() const;
double getDisplayWidthDouble() const;
/**
* Returns the height of this XojPageView as displayed
* on the display taking into account the current zoom
*/
int getDisplayHeight() const;
double getDisplayHeightDouble() const;
/**
* Returns the x coordinate of this XojPageView with
* respect to the display
*/
int getX() const override;
/**
* Returns the y coordinate of this XojPageView with
* respect to the display
*/
int getY() const override;
const TexImage* getSelectedTex() const;
const Text* getSelectedText() const;
xoj::util::Rectangle<double> getRect() const;
public: // event handler
bool onButtonPressEvent(const PositionInputData& pos);
bool onButtonReleaseEvent(const PositionInputData& pos);
bool onButtonDoublePressEvent(const PositionInputData& pos);
bool onButtonTriplePressEvent(const PositionInputData& pos);
bool onMotionNotifyEvent(const PositionInputData& pos);
void onSequenceCancelEvent(DeviceId id);
void onTapEvent(const PositionInputData& pos);
/**
* This event fires after onButtonPressEvent and also
* if no input sequence is actively running and a stylus button was pressed
*/
bool onButtonClickEvent(const PositionInputData& pos);
/**
* This method actually repaints the XojPageView, triggering
* a rerender call if necessary
*/
bool paintPage(cairo_t* cr, GdkRectangle* rect);
void deleteLaserPointerHandler();
public: // listener
void rectChanged(xoj::util::Rectangle<double>& rect) override;
void rangeChanged(Range& range) override;
void pageChanged() override;
void elementChanged(const Element* elem) override;
void elementsChanged(const std::vector<const Element*>& elements, const Range& range) override;
private:
void startText(double x, double y);
void drawLoadingPage(cairo_t* cr);
/**
* @brief Make and display a popover dialog near the given location.
*
* @param rect specifies the location of the dialog.
* @param child is added to the dialog before displaying.
* @returns a pointer to the popover dialog.
*/
GtkWidget* makePopover(const XojPdfRectangle& rect, GtkWidget* child);
/**
* @brief Display a popover with link-related actions, if one
* is at a given location on the page.
*
* @param page is the current page
* @param targetX
* @param targetY the link must contain (targetX, targetY)
* @returns true iff a URI link exists near/at (targetX, targetY) => a popover was shown
*/
bool displayLinkPopover(std::shared_ptr<XojPdfPage> page, double targetX, double targetY);
void setX(int x);
void setY(int y);
void setMappedRowCol(int row, int col); // row, column assigned by mapper during layout.
/**
* Shows the floating toolbox at the location of an input event
*/
void showFloatingToolbox(const PositionInputData& pos);
/**
* Shows the PDF toolbox at the location of an input event
*/
void showPdfToolbox(const PositionInputData& pos);
void deleteView(xoj::view::OverlayView* v);
private:
PageRef page;
XournalView* xournal = nullptr;
Settings* settings = nullptr;
std::unique_ptr<EraseHandler> eraser;
std::unique_ptr<InputHandler> inputHandler;
std::vector<std::unique_ptr<xoj::view::OverlayView>> overlayViews;
/**
* The selected (while selection)
*/
std::unique_ptr<Selector> selector;
/**
* The text editor
*/
std::unique_ptr<TextEditor> textEditor;
/**
* For image insertion with size (selects the size)
*/
std::unique_ptr<ImageSizeSelection> imageSizeSelection;
std::unique_ptr<LaserPointerHandler> laserPointer;
/**
* For keeping old text changes to undo!
*/
Text* oldtext;
bool visible = true;
bool selected = false;
xoj::view::Mask buffer;
std::mutex drawingMutex;
bool inEraser = false;
/**
* Vertical Space
*/
std::unique_ptr<VerticalToolHandler> verticalSpace;
/**
* Search handling
*/
std::unique_ptr<SearchControl> search;
std::mutex repaintRectMutex;
std::vector<xoj::util::Rectangle<double>> rerenderRects;
bool rerenderComplete = false;
bool sizeChanged = false;
int dispX{}; // position on display - set in Layout::layoutPages
int dispY{};
int mappedRow{};
int mappedCol{};
DeviceId currentSequenceDeviceId;
friend class RenderJob;
friend class InputHandler;
friend class BaseSelectObject;
friend class SelectObject;
friend class PlayObject;
friend class PdfFloatingToolbox;
// only function allowed to setX(), setY(), setMappedRowCol():
friend void Layout::layoutPages(int width, int height);
};
@@ -1,204 +0,0 @@
/*
* Xournal++
*
* Helper classes to find / select objects
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
// No include needed, this is included after PageView.h
#include <limits>
#include <mutex>
#include <optional>
#include "control/AudioController.h"
#include "control/Control.h"
#include "control/layer/LayerController.h"
#include "control/settings/Settings.h"
#include "control/tools/EditSelection.h"
#include "gui/PageView.h"
#include "model/Layer.h"
#include "model/XojPage.h"
#include "util/safe_casts.h"
#include "XournalView.h"
class BaseSelectObject {
public:
BaseSelectObject(XojPageView* view): view(view) {}
virtual ~BaseSelectObject() = default;
public:
bool findAt(double x, double y, bool multiLayer = false, bool clearSelection = true) {
this->x = x;
this->y = y;
Control* ctrl = this->view->getXournal()->getControl();
if (clearSelection) {
ctrl->clearSelection();
}
std::shared_lock lock(*ctrl->getDocument());
if (multiLayer) {
const auto& layers = this->view->getPage()->getLayers();
size_t layerNo = layers.size();
for (auto l = layers.rbegin(); l != layers.rend(); l++, layerNo--) {
if (checkLayer(*l)) {
ctrl->getLayerController()->switchToLay(as_unsigned(std::distance(l, layers.rend())));
return true;
}
}
return false;
} else {
Layer* layer = this->view->getPage()->getSelectedLayer();
return checkLayer(layer);
}
}
protected:
virtual bool checkLayer(const Layer* l) = 0;
protected:
XojPageView* view{};
double x{0};
double y{0};
};
class SelectObject: public BaseSelectObject {
public:
SelectObject(XojPageView* view): BaseSelectObject(view), match(nullptr) {}
~SelectObject() override = default;
bool at(double x, double y, bool multiLayer = false) {
findAt(x, y, multiLayer);
if (match) {
Control* ctrl = view->getXournal()->getControl();
auto sel = SelectionFactory::createFromElementOnActiveLayer(ctrl, view->getPage(), view, match, matchIndex);
view->xournal->setSelection(sel.release());
view->repaintPage();
return true;
}
return false;
}
bool atAggregate(double x, double y) {
EditSelection* previousSelection = view->getXournal()->getSelection();
xoj_assert(previousSelection);
findAt(x, y, false, false);
if (match) {
auto sel = SelectionFactory::addElementFromActiveLayer(view->getXournal()->getControl(), previousSelection,
match, matchIndex);
view->getXournal()->setSelection(sel.release());
view->repaintPage();
return true;
}
return false;
}
static constexpr double ACTION_RADIUS = 5.;
bool checkLayer(const Layer* l) override {
double minDistance = ACTION_RADIUS;
// Iterate starting from the front-most element
Element::Index pos = as_signed(l->getElementsView().size());
for (auto it = l->getElementsView().rbegin(); it < l->getElementsView().rend(); ++it) {
pos--;
// First perform a rough check to avoid expensive calls to Stroke::distanceTo()
if ((*it)->intersectsArea(x - minDistance, y - minDistance, 2. * minDistance, 2. * minDistance)) {
double d = (*it)->distanceTo(x, y);
if (d == 0.0) {
this->match = *it;
this->matchIndex = pos;
return true;
}
if (d < minDistance) {
this->match = *it;
this->matchIndex = pos;
minDistance = d;
// Keep going, we may find something closer
}
}
}
return minDistance != ACTION_RADIUS;
}
private:
const Element* match;
Element::Index matchIndex;
};
class PlayObject: public BaseSelectObject {
public:
PlayObject(XojPageView* view): BaseSelectObject(view), playbackStatus() {}
~PlayObject() override = default;
struct Status {
bool success;
fs::path filename;
};
std::optional<Status> playbackStatus;
public:
bool at(double x, double y, bool multiLayer = false) { return findAt(x, y, multiLayer); }
protected:
static constexpr double ACTION_RADIUS = 15.;
/// Plays every element of the layer that are closer than ACTION_RADIUS
bool checkLayer(const Layer* l) override {
bool found = false;
for (auto&& e: l->getElementsView()) {
if (auto* audio = dynamic_cast<const AudioElement*>(e); audio) {
// First perform a rough check to avoid expensive calls to Stroke::distanceTo()
if (audio->intersectsArea(x - ACTION_RADIUS, y - ACTION_RADIUS, 2. * ACTION_RADIUS,
2. * ACTION_RADIUS)) {
double d = audio->distanceTo(x, y);
if (d < ACTION_RADIUS) {
found = playElement(audio) || found;
}
}
}
}
return found;
}
bool playElement(const AudioElement* s) {
#ifdef ENABLE_AUDIO
size_t ts = s->getTimestamp();
if (auto fn = s->getAudioFilename(); !fn.empty()) {
if (!fn.has_parent_path() || fs::weakly_canonical(fn.parent_path()) == "/") {
auto const& path = view->settings->getAudioFolder();
// Assume path exists
fn = path / fn;
}
auto* audioController = view->getXournal()->getControl()->getAudioController();
if (!audioController) {
g_warning("Audio has been disabled");
return false;
}
bool success = audioController->startPlayback(fn, (unsigned int)ts);
playbackStatus = {success, std::move(fn)};
return success;
}
#else
g_warning("Audio support was disabled at compile time");
#endif
return false;
}
};
@@ -1,57 +0,0 @@
#include "PaperFormatUtils.h"
#include "util/GListView.h"
#include "util/StringUtils.h"
#include "util/raii/GObjectSPtr.h"
using xoj::util::GtkPaperSizeUPtr;
void PaperFormatUtils::loadDefaultPaperSizes(PaperFormatMenuOptionVector& paperSizes) {
GList* default_sizes = gtk_paper_size_get_paper_sizes(false);
for (auto& s: GListView<GtkPaperSize>(default_sizes)) {
std::string name = gtk_paper_size_get_name(&s);
if (name == GTK_PAPER_NAME_A3 || name == GTK_PAPER_NAME_A4 || name == GTK_PAPER_NAME_A5 ||
name == GTK_PAPER_NAME_LETTER || name == GTK_PAPER_NAME_LEGAL) {
paperSizes.emplace_back(GtkPaperSizeUPtr(&s));
} else {
gtk_paper_size_free(&s);
}
}
g_list_free(default_sizes);
// Name format: ftp://ftp.pwg.org/pub/pwg/candidates/cs-pwgmsn10-20020226-5101.1.pdf
paperSizes.emplace_back(GtkPaperSizeUPtr(gtk_paper_size_new("custom_16x9_320x180mm")));
paperSizes.emplace_back(GtkPaperSizeUPtr(gtk_paper_size_new("custom_4x3_320x240mm")));
}
void PaperFormatUtils::fillPaperFormatDropDown(const PaperFormatMenuOptionVector& menuOptions,
GtkComboBox* paperFormatComboBox) {
// Todo (gtk4) Use GtkDropDown instead and use another model accordingly
GtkCellRenderer* cell = gtk_cell_renderer_text_new();
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(paperFormatComboBox), cell, true);
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(paperFormatComboBox), cell, "text", 0, nullptr);
GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
gtk_combo_box_set_model(GTK_COMBO_BOX(paperFormatComboBox), GTK_TREE_MODEL(store));
g_object_unref(store); // store is now owned by paperFormatComboBox
for (auto& menuOption: menuOptions) {
GtkTreeIter iter;
gtk_list_store_append(store, &iter);
if (menuOption.index()) {
auto& size = std::get<GtkPaperSizeUPtr>(menuOption);
std::string displayName = gtk_paper_size_get_display_name(size.get());
if (StringUtils::startsWith(displayName, "custom_")) {
displayName = displayName.substr(7);
}
gtk_list_store_set(store, &iter, 0, displayName.c_str(), -1);
gtk_list_store_set(store, &iter, 1, size.get(), -1);
} else {
// When special non-GtkPaperSize option
const std::string_view displayName = std::get<std::string>(menuOption);
gtk_list_store_set(store, &iter, 0, displayName.data(), -1);
gtk_list_store_set(store, &iter, 1, nullptr, -1);
}
}
}
@@ -1,42 +0,0 @@
/*
* Xournal++
*
* Paper format utilities
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#include <memory>
#include <variant>
#include <vector>
#include <gtk/gtk.h>
#include "util/raii/GtkPaperSizeUPtr.h"
#pragma once
namespace PaperFormatUtils {
/**
* @brief A vector of menu options, either being a GtkPaperSize or the displayed name of the option.
* @details The variant facilitates adding options which can't be expressed as a GtkPaperSize (e.g. a custom option
* or a copy current page format option)
*/
using PaperFormatMenuOptionVector = std::vector<std::variant<std::string, xoj::util::GtkPaperSizeUPtr>>;
/**
* @brief Loads the default PaperSizes into the passed vector
* @param menuOptions The vector containg the menu options
*/
void loadDefaultPaperSizes(PaperFormatMenuOptionVector& menuOptions);
/**
* @brief Adds the entries passed as vector of GtkPaperSize entries and/or special entries to the given combo box
* @param menuOptions The vector of menu options
* @param paperFormatComboBox A pointer to the combo box which should be configured as a paper format dropdown
*/
void fillPaperFormatDropDown(const PaperFormatMenuOptionVector& menuOptions, GtkComboBox* paperFormatComboBox);
} // namespace PaperFormatUtils
@@ -1,239 +0,0 @@
#include "PdfFloatingToolbox.h"
#include <algorithm> // for max, min
#include <cmath> // for abs
#include <cstddef> // for size_t
#include <memory>
#include <string> // for string
#include <utility> // for move
#include <vector> // for vector
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
#include <gtk/gtk.h>
#include "control/Control.h" // for Control
#include "control/ToolEnums.h" // for ToolType, TOOL_SELECT_PDF_TEXT_LI...
#include "control/ToolHandler.h" // for ToolHandler
#include "control/tools/PdfElemSelection.h"
#include "gui/PageView.h" // for XojPageView
#include "gui/XournalView.h" // for XournalView
#include "model/Document.h" // for Document
#include "model/Layer.h" // for Layer
#include "model/PageRef.h" // for PageRef
#include "model/Point.h" // for Point
#include "model/Stroke.h" // for Stroke, BUTT, StrokeTool::HIGHLIG...
#include "model/XojPage.h" // for XojPage
#include "undo/GroupUndoAction.h" // for GroupUndoAction
#include "undo/InsertUndoAction.h" // for InsertUndoAction
#include "undo/UndoAction.h" // for UndoAction
#include "undo/UndoRedoHandler.h" // for UndoRedoHandler
#include "util/Assert.h" // for xoj_assert
#include "util/gtk4_helper.h" // for gtk_widget_get_clipboard
#include "MainWindow.h" // for MainWindow
PdfFloatingToolbox::PdfFloatingToolbox(MainWindow* theMainWindow, GtkOverlay* overlay):
theMainWindow(theMainWindow), overlay(overlay, xoj::util::ref), position({0, 0}) {
this->floatingToolbox = theMainWindow->get("pdfFloatingToolbox");
gtk_overlay_add_overlay(overlay, this->floatingToolbox);
gtk_overlay_set_overlay_pass_through(overlay, this->floatingToolbox, true);
g_signal_connect(overlay, "get-child-position", G_CALLBACK(this->getOverlayPosition), this);
g_signal_connect(theMainWindow->get("pdfTbHighlight"), "clicked", G_CALLBACK(this->highlightCb), this);
g_signal_connect(theMainWindow->get("pdfTbCopyText"), "clicked", G_CALLBACK(this->copyTextCb), this);
g_signal_connect(theMainWindow->get("pdfTbUnderline"), "clicked", G_CALLBACK(this->underlineCb), this);
g_signal_connect(theMainWindow->get("pdfTbStrikethrough"), "clicked", G_CALLBACK(this->strikethroughCb), this);
g_signal_connect(theMainWindow->get("pdfTbChangeType"), "clicked", G_CALLBACK(this->switchSelectTypeCb), this);
this->clearSelection();
this->hide();
}
PdfFloatingToolbox::~PdfFloatingToolbox() = default;
PdfElemSelection* PdfFloatingToolbox::getSelection() const { return this->pdfElemSelection.get(); }
bool PdfFloatingToolbox::hasSelection() const { return this->getSelection() != nullptr; }
void PdfFloatingToolbox::clearSelection() { this->pdfElemSelection.reset(); }
auto PdfFloatingToolbox::newSelection(double x, double y) -> const PdfElemSelection* {
this->pdfElemSelection = std::make_unique<PdfElemSelection>(x, y, this->theMainWindow->getControl());
return this->pdfElemSelection.get();
}
void PdfFloatingToolbox::show(int x, int y) {
xoj_assert(this->getSelection());
// (x, y) are in the gtk window's coordinates.
// However, we actually show the toolbox in the overlay's coordinate system.
gtk_widget_translate_coordinates(gtk_widget_get_toplevel(this->floatingToolbox), GTK_WIDGET(overlay.get()), x, y,
&this->position.x, &this->position.y);
this->show();
// Record the color now: the active tool may change while the toolbox is up (e.g. if the tool is linked to a button)
this->color = theMainWindow->getXournal()->getControl()->getToolHandler()->getColor();
}
void PdfFloatingToolbox::hide() {
if (isHidden())
return;
gtk_widget_hide(this->floatingToolbox);
}
auto PdfFloatingToolbox::getOverlayPosition(GtkOverlay* overlay, GtkWidget* widget, GdkRectangle* allocation,
PdfFloatingToolbox* self) -> gboolean {
if (widget == self->floatingToolbox) {
// Get existing width and height
GtkRequisition natural;
gtk_widget_get_preferred_size(widget, nullptr, &natural);
allocation->width = natural.width;
allocation->height = natural.height;
// Make sure the "pdfFloatingToolbox" is fully displayed.
const int gap = 5;
// By default, we show the toolbox below and to the right of the selected text.
// If the toolbox will go out of the window, then we'll flip the corresponding directions.
GtkAllocation windowAlloc{};
gtk_widget_get_allocation(GTK_WIDGET(overlay), &windowAlloc);
bool rightOK = self->position.x + allocation->width + gap <= windowAlloc.width;
bool bottomOK = self->position.y + allocation->height + gap <= windowAlloc.height;
allocation->x = rightOK ? self->position.x + gap : self->position.x - allocation->width - gap;
allocation->y = bottomOK ? self->position.y + gap : self->position.y - allocation->height - gap;
return true;
}
return false;
}
void PdfFloatingToolbox::userCancelSelection() {
this->pdfElemSelection.reset();
this->hide();
}
void PdfFloatingToolbox::highlightCb(GtkButton* button, PdfFloatingToolbox* pft) {
int markerOpacity = pft->theMainWindow->getControl()->getToolHandler()->getSelectPDFTextMarkerOpacity();
pft->createStrokes(PdfMarkerStyle::POS_TEXT_MIDDLE, PdfMarkerStyle::WIDTH_TEXT_HEIGHT, markerOpacity);
pft->userCancelSelection();
}
void PdfFloatingToolbox::copyTextCb(GtkButton* button, PdfFloatingToolbox* pft) {
pft->copyTextToClipboard();
pft->userCancelSelection();
}
void PdfFloatingToolbox::underlineCb(GtkButton* button, PdfFloatingToolbox* pft) {
pft->createStrokes(PdfMarkerStyle::POS_TEXT_BOTTOM, PdfMarkerStyle::WIDTH_TEXT_LINE, 230);
pft->userCancelSelection();
}
void PdfFloatingToolbox::strikethroughCb(GtkButton* button, PdfFloatingToolbox* pft) {
pft->createStrokes(PdfMarkerStyle::POS_TEXT_MIDDLE, PdfMarkerStyle::WIDTH_TEXT_LINE, 230);
pft->userCancelSelection();
}
void PdfFloatingToolbox::show() {
gtk_widget_hide(this->floatingToolbox); // force showing in new position
gtk_widget_show_all(this->floatingToolbox);
}
void PdfFloatingToolbox::copyTextToClipboard() {
GtkClipboard* clipboard = gtk_widget_get_clipboard(this->theMainWindow->getWindow());
if (const std::string& text = this->pdfElemSelection->getSelectedText(); !text.empty()) {
gtk_clipboard_set_text(clipboard, text.c_str(), -1);
}
}
void PdfFloatingToolbox::createStrokes(PdfMarkerStyle position, PdfMarkerStyle width, int markerOpacity) {
const size_t pdfPageNo = this->pdfElemSelection->getSelectionPageNr();
const size_t currentPage = theMainWindow->getXournal()->getCurrentPage();
// Get the PDF page that the current page corresponds to.
// It should be the same as the PDF page of the selection.
auto doc = this->theMainWindow->getControl()->getDocument();
doc->lock_shared();
const size_t pdfPageOfCurrentPage = doc->getPage(currentPage)->getPdfPageNr();
doc->unlock_shared();
if (pdfPageOfCurrentPage != pdfPageNo) {
// There's probably a bug that violates our assumptions, so no-op.
g_warning("The current page's PDF page is not the same as the PDF page of the selection!");
return;
}
const auto textRects = this->pdfElemSelection->getSelectedTextRects();
if (textRects.empty()) {
return;
}
auto* control = this->theMainWindow->getControl();
PageRef page = control->getCurrentPage();
Layer* layer = page->getSelectedLayer();
Range dirtyRange;
std::vector<ElementPtr> strokes;
for (XojPdfRectangle rect: textRects) {
const double topOfLine = std::min(rect.y1, rect.y2);
const double middleOfLine = (rect.y1 + rect.y2) / 2;
const double bottomOfLine = std::max(rect.y1, rect.y2);
const double rectWidth = std::abs(rect.y2 - rect.y1);
// the center line position of stroke
const double h = position == PdfMarkerStyle::POS_TEXT_BOTTOM ? bottomOfLine :
position == PdfMarkerStyle::POS_TEXT_MIDDLE ? middleOfLine :
topOfLine;
// the width of stroke
const double w = width == PdfMarkerStyle::WIDTH_TEXT_LINE ? 1 : rectWidth;
auto stroke = std::make_unique<Stroke>();
stroke->setColor(this->color);
stroke->setFill(markerOpacity);
stroke->setToolType(StrokeTool::HIGHLIGHTER);
stroke->setWidth(w);
stroke->addPoint(Point(rect.x1, h, -1));
stroke->addPoint(Point(rect.x2, h, -1));
stroke->setStrokeCapStyle(StrokeCapStyle::BUTT);
dirtyRange.addPoint(rect.x1, h - 0.5 * w);
dirtyRange.addPoint(rect.x2, h + 0.5 * w);
strokes.push_back(std::move(stroke));
}
std::vector<const Element*> strokePtrs(strokes.size());
std::transform(strokes.begin(), strokes.end(), strokePtrs.begin(), [](auto& e) { return e.get(); });
doc->lock();
for (auto&& s: strokes) {
layer->addElement(std::move(s));
}
doc->unlock();
page->fireElementsChanged(strokePtrs, dirtyRange);
auto undoAct = std::make_unique<GroupUndoAction>();
for (auto* stroke: strokePtrs) {
undoAct->addAction(std::make_unique<InsertUndoAction>(page, layer, stroke));
}
control->getUndoRedoHandler()->addUndoAction(std::move(undoAct));
}
void PdfFloatingToolbox::switchSelectTypeCb(GtkButton* button, PdfFloatingToolbox* pft) {
ToolType type = pft->theMainWindow->getControl()->getToolHandler()->getToolType();
type = type == ToolType::TOOL_SELECT_PDF_TEXT_LINEAR ? ToolType::TOOL_SELECT_PDF_TEXT_RECT :
ToolType::TOOL_SELECT_PDF_TEXT_LINEAR;
pft->theMainWindow->getControl()->selectTool(type);
pft->pdfElemSelection->setToolType(type);
pft->pdfElemSelection->finalizeSelection(PdfElemSelection::selectionStyleForToolType(type));
}
bool PdfFloatingToolbox::isHidden() const { return !gtk_widget_is_visible(this->floatingToolbox); }
@@ -1,97 +0,0 @@
#pragma once
#include <cstdint> // for uint8_t
#include <memory> // for unique_ptr
#include <gdk/gdk.h> // for GdkRectangle
#include <glib.h> // for gboolean
#include <gtk/gtk.h> // for GtkButton, GtkOverlay
#include "pdf/base/XojPdfPage.h" // for XojPdfPageSelectionStyle
#include "util/Color.h" // for Color
#include "util/raii/GObjectSPtr.h" // for GObjectSPtr
class MainWindow;
class PdfElemSelection;
enum class PdfMarkerStyle : uint8_t {
POS_TEXT_BOTTOM = 0,
POS_TEXT_MIDDLE,
POS_TEXT_TOP,
WIDTH_TEXT_LINE,
WIDTH_TEXT_HEIGHT
};
class PdfFloatingToolbox {
public:
PdfFloatingToolbox(MainWindow* theMainWindow, GtkOverlay* overlay);
PdfFloatingToolbox& operator=(const PdfFloatingToolbox&) = delete;
PdfFloatingToolbox(const PdfFloatingToolbox&) = delete;
PdfFloatingToolbox& operator=(PdfFloatingToolbox&&) = delete;
PdfFloatingToolbox(PdfFloatingToolbox&&) = delete;
~PdfFloatingToolbox();
public:
/// Show the toolbox at the provided coordinates (relative to the application GTK window).
///
/// Must have an active selection.
void show(int x, int y);
/// Hide the floating toolbox widget (keeping the current selection).
void hide();
/// Returns true if the toolbox is currently hidden.
bool isHidden() const;
/// Returns the selection, or nullptr if no PDF element is selected.
PdfElemSelection* getSelection() const;
/// Clears the current selection (without any visual effects).
void clearSelection();
/// Create a new selection (without any visual effects)
const PdfElemSelection* newSelection(double x, double y);
/// Returns true iff a PDF element is selected;
bool hasSelection() const;
/// Cancel the selection, rerender the page, and hide the toolbox.
void userCancelSelection();
/// Track selection style used for unfinalized selections
XojPdfPageSelectionStyle selectionStyle = XojPdfPageSelectionStyle::Linear;
/// Copy Selection to the Clipboard.
void copyTextToClipboard();
private:
void show();
static gboolean getOverlayPosition(GtkOverlay* overlay, GtkWidget* widget, GdkRectangle* allocation,
PdfFloatingToolbox* self);
static void switchSelectTypeCb(GtkButton* button, PdfFloatingToolbox* pft);
static void strikethroughCb(GtkButton* button, PdfFloatingToolbox* pft);
static void underlineCb(GtkButton* button, PdfFloatingToolbox* pft);
static void copyTextCb(GtkButton* button, PdfFloatingToolbox* pft);
static void highlightCb(GtkButton* button, PdfFloatingToolbox* pft);
void createStrokes(PdfMarkerStyle position, PdfMarkerStyle width, int markerOpacity);
private:
GtkWidget* floatingToolbox;
MainWindow* theMainWindow;
Color color; ///< Used for strokes/highlighting
/// The overlay that the toolbox should be displayed in.
xoj::util::GObjectSPtr<GtkOverlay> overlay;
std::unique_ptr<PdfElemSelection> pdfElemSelection;
struct {
int x;
int y;
} position;
};
@@ -1,23 +0,0 @@
/*
* Xournal++
*
* Abstract class for creating popovers
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <gtk/gtk.h> // for GtkWidget
class PopoverFactory {
public:
PopoverFactory() = default;
virtual ~PopoverFactory() = default;
/// Creates a GtkPopover. The returned ref is floating
virtual GtkWidget* createPopover() const = 0;
};
@@ -1,29 +0,0 @@
#include "RepaintHandler.h"
#include <gtk/gtk.h> // for gtk_widget_queue_draw
#include "gui/widgets/XournalWidget.h" // for gtk_xournal_repaint_area
#include "PageView.h" // for XojPageView
#include "XournalView.h" // for XournalView
RepaintHandler::RepaintHandler(XournalView* xournal): xournal(xournal) {}
RepaintHandler::~RepaintHandler() { this->xournal = nullptr; }
void RepaintHandler::repaintPage(const XojPageView* view) {
int x1 = view->getX();
int y1 = view->getY();
int x2 = x1 + view->getDisplayWidth();
int y2 = y1 + view->getDisplayHeight();
gtk_xournal_repaint_area(this->xournal->getWidget(), x1, y1, x2, y2);
}
void RepaintHandler::repaintPageArea(const XojPageView* view, int x1, int y1, int x2, int y2) {
int x = view->getX();
int y = view->getY();
gtk_xournal_repaint_area(this->xournal->getWidget(), x + x1, y + y1, x + x2, y + y2);
}
void RepaintHandler::repaintPageBorder(const XojPageView* view) { gtk_widget_queue_draw(this->xournal->getWidget()); }
@@ -1,40 +0,0 @@
/*
* Xournal++
*
* Class for render and repaint pages
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
class XojPageView;
class XournalView;
class RepaintHandler {
public:
RepaintHandler(XournalView* xournal);
virtual ~RepaintHandler();
public:
/**
* Repaint a page
*/
void repaintPage(const XojPageView* view);
/**
* Repaint a page area, coordinates are in view coordinates
*/
void repaintPageArea(const XojPageView* view, int x1, int y1, int x2, int y2);
/**
* Repaints the page border (at least)
*/
void repaintPageBorder(const XojPageView* view);
private:
XournalView* xournal;
};
@@ -1,198 +0,0 @@
#include "SearchBar.h"
#include <string> // for allocator, string
#include <gdk/gdk.h> // for GdkEventKey, GDK_SHIFT_MASK
#include <gdk/gdkkeysyms.h> // for GDK_KEY_Return
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
#include <glib.h> // for g_free, g_strdup_printf
#include "control/Control.h" // for Control
#include "control/ScrollHandler.h" // for ScrollHandler
#include "control/zoom/ZoomControl.h" // for ZoomControl
#include "gui/MainWindow.h" // for MainWindow
#include "model/Document.h" // for Document
#include "util/PlaceholderString.h" // for PlaceholderString
#include "util/i18n.h" // for _, FC, _F
SearchBar::SearchBar(Control* control): control(control) {
MainWindow* win = control->getWindow();
GtkWidget* close = win->get("buttonCloseSearch");
g_signal_connect(close, "clicked", G_CALLBACK(buttonCloseSearchClicked), this);
GtkWidget* next = win->get("btSearchForward");
GtkWidget* previous = win->get("btSearchBack");
g_signal_connect(next, "clicked",
G_CALLBACK(+[](GtkButton*, gpointer d) { static_cast<SearchBar*>(d)->searchNext(); }), this);
g_signal_connect(previous, "clicked",
G_CALLBACK(+[](GtkButton*, gpointer d) { static_cast<SearchBar*>(d)->searchPrevious(); }), this);
GtkWidget* searchTextField = win->get("searchTextField");
g_signal_connect(searchTextField, "search-changed", G_CALLBACK(searchTextChangedCallback), this);
g_signal_connect(searchTextField, "stop-search",
G_CALLBACK(+[](GtkSearchEntry*, gpointer d) { static_cast<SearchBar*>(d)->showSearchBar(false); }),
this);
g_signal_connect(searchTextField, "next-match", G_CALLBACK(+[](GtkSearchEntry* e, gpointer d) {
static_cast<SearchBar*>(d)->searchNext();
// Grab focus again since searching will take away focus
gtk_widget_grab_focus(GTK_WIDGET(e));
}),
this);
g_signal_connect(searchTextField, "previous-match", G_CALLBACK(+[](GtkSearchEntry* e, gpointer d) {
static_cast<SearchBar*>(d)->searchPrevious();
// Grab focus again since searching will take away focus
gtk_widget_grab_focus(GTK_WIDGET(e));
}),
this);
#if GTK_MAJOR_VERSION == 3
GtkBindingSet* bindingSet = gtk_binding_set_by_class(GTK_SEARCH_ENTRY_GET_CLASS(searchTextField));
gtk_binding_entry_add_signal(bindingSet, GDK_KEY_Return, GdkModifierType(0), "next-match", 0);
gtk_binding_entry_add_signal(bindingSet, GDK_KEY_Return, GDK_SHIFT_MASK, "previous-match", 0);
#else
GtkEventController* ctrl = gtk_shortcut_controller_new();
gtk_shortcut_controller_add_shortcut(GTK_SHORTCUT_CONTROLLER(ctrl),
gtk_shortcut_new(gtk_keyval_trigger_new(GDK_KEY_Return, GdkModifierType(0)),
gtk_signal_action_new("next-match")));
gtk_shortcut_controller_add_shortcut(GTK_SHORTCUT_CONTROLLER(ctrl),
gtk_shortcut_new(gtk_keyval_trigger_new(GDK_KEY_Return, GDK_SHIFT_MASK),
gtk_signal_action_new("previous-match")));
gtk_widget_add_controller(searchTextField, ctrl);
#endif
cssTextFild = gtk_css_provider_new();
gtk_style_context_add_provider(gtk_widget_get_style_context(win->get("searchTextField")),
GTK_STYLE_PROVIDER(cssTextFild), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
SearchBar::~SearchBar() { this->control = nullptr; }
auto SearchBar::searchTextonCurrentPage(const char* text, size_t index, size_t* occurrences, XojPdfRectangle* matchRect)
-> bool {
size_t p = control->getCurrentPageNo();
this->page = p;
return control->searchTextOnPage(text, p, index, occurrences, matchRect);
}
void SearchBar::search(const char* text) {
MainWindow* win = control->getWindow();
GtkWidget* lbSearchState = win->get("lbSearchState");
bool found = true;
this->indexInPage = 0;
if (*text != 0) {
found = searchTextonCurrentPage(text, 1, &this->occurrences, nullptr);
if (found) {
if (occurrences == 1) {
gtk_label_set_text(GTK_LABEL(lbSearchState), _("Text found once on this page"));
} else {
char* msg = g_strdup_printf(_("Text found %zu times on this page"), occurrences);
gtk_label_set_text(GTK_LABEL(lbSearchState), msg);
g_free(msg);
}
} else {
gtk_label_set_text(GTK_LABEL(lbSearchState), _("Text not found"));
}
} else {
searchTextonCurrentPage("", 1, nullptr, nullptr);
gtk_label_set_text(GTK_LABEL(lbSearchState), "");
}
if (found) {
gtk_css_provider_load_from_data(cssTextFild, "GtkSearchEntry {}", -1, nullptr);
} else {
gtk_css_provider_load_from_data(cssTextFild, "GtkSearchEntry { color: #ff0000; }", -1, nullptr);
}
}
void SearchBar::searchTextChangedCallback(GtkSearchEntry* entry, SearchBar* searchBar) {
const char* text = gtk_entry_get_text(GTK_ENTRY(entry));
searchBar->search(text);
}
void SearchBar::buttonCloseSearchClicked(GtkButton* button, SearchBar* searchBar) { searchBar->showSearchBar(false); }
template <class Fun>
void SearchBar::search(Fun next) {
MainWindow* win = control->getWindow();
GtkWidget* searchTextField = win->get("searchTextField");
const char* text = gtk_entry_get_text(GTK_ENTRY(searchTextField));
GtkWidget* lbSearchState = win->get("lbSearchState");
if (*text == 0) {
return;
}
const size_t originalPage = page;
XojPdfRectangle matchRect = XojPdfRectangle();
// Search backwards through the pages, wrapping around if needed.
for (;;) {
next(text);
const bool found = control->searchTextOnPage(text, page, indexInPage, &occurrences, &matchRect);
if (found) {
control->getScrollHandler()->scrollToPage(page, matchRect);
control->getScrollHandler();
gtk_label_set_text(GTK_LABEL(lbSearchState),
(occurrences == 1 ? FC(_F("Text found once on page {1}") % (page + 1)) :
FC(_F("Text found {1} times on page {2} ({3} of {1})") %
occurrences % (page + 1) % indexInPage)));
return;
}
if (page == originalPage) {
gtk_label_set_text(GTK_LABEL(lbSearchState), _("Text not found, searched on all pages"));
return;
}
}
}
void SearchBar::searchNext() {
size_t pageCount = control->getDocument()->getPageCount();
search([&](const char* text) {
indexInPage++;
if (indexInPage > occurrences) {
control->searchTextOnPage(text, page, 1, &occurrences, nullptr); // clear the active marker
page++;
if (page >= pageCount) {
page = 0;
}
indexInPage = 1;
}
});
}
void SearchBar::searchPrevious() {
size_t pageCount = control->getDocument()->getPageCount();
search([&](const char* text) {
indexInPage--;
if (indexInPage == 0 || indexInPage >= occurrences) {
control->searchTextOnPage(text, page, 1, &occurrences, nullptr); // clear the active marker
page--;
if (page > pageCount) {
page = pageCount - 1;
}
control->searchTextOnPage(text, page, 1, &occurrences, nullptr);
indexInPage = occurrences;
}
});
}
void SearchBar::showSearchBar(bool show) {
MainWindow* win = control->getWindow();
GtkWidget* searchBar = win->get("searchBar");
if (show) {
GtkWidget* searchTextField = win->get("searchTextField");
gtk_widget_show_all(searchBar);
gtk_widget_grab_focus(searchTextField);
this->indexInPage = 0;
} else {
gtk_widget_hide(searchBar);
const size_t pageCount = control->getDocument()->getPageCount();
for (size_t i = pageCount - 1; i < pageCount; i--) {
control->searchTextOnPage("", i, 0, nullptr, nullptr);
}
}
}
@@ -1,65 +0,0 @@
/*
* Xournal++
*
* Handles the searchbar and search events
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <gtk/gtk.h> // for GtkButton, GtkEntry
#include <gtk/gtkcssprovider.h> // for GtkCssProvider
class Control;
class XojPdfRectangle;
class SearchBar {
public:
SearchBar(Control* control);
virtual ~SearchBar();
void showSearchBar(bool show);
private:
static void buttonCloseSearchClicked(GtkButton* button, SearchBar* searchBar);
static void searchTextChangedCallback(GtkSearchEntry* entry, SearchBar* searchBar);
static void buttonNextSearchClicked(GtkButton* button, SearchBar* searchBar);
static void buttonPreviousSearchClicked(GtkButton* button, SearchBar* searchBar);
/**
* @brief Searches the entire document until a match is found, starting from `page = next(currentPage)` and
* iterating through the pages via page = next(page). The search stops after the first page with at least one match.
* All the match on that page are stored in XojPageView::search of the corresponding page. The current page is not
* search!
* @param next The parameter `next` must be convertible to size_t(size_t) and satisfy the following assertions
* * Iterating from page = next(currentPage) by page = next(page) must reach page == currentPage at
* some point.
* * If page is a valid page number, then so is next(page).
*/
template <class Fun>
void search(Fun next);
/**
* @brief Named specialization of search(), where next(page) = (page + 1) % pageCount
*/
void searchNext();
/**
* @brief Named specialization of search(), where next(page) = (page + pageCount - 1) % pageCount
*/
void searchPrevious();
void search(const char* text);
bool searchTextonCurrentPage(const char* text, size_t index, size_t* occurrences, XojPdfRectangle* matchRect);
private:
Control* control;
GtkCssProvider* cssTextFild;
size_t page = 0;
size_t indexInPage = 0;
size_t occurrences = 0;
};
-217
View File
@@ -1,217 +0,0 @@
#include "Shadow.h"
#include <cmath>
#include "ShadowCode.c"
Shadow* Shadow::instance = new Shadow();
using u8ptr = const unsigned char*;
Shadow::Shadow() {
const int sBrSize = shadowBottomRightSize;
const int sTlSize = shadowTopLeftSize;
this->edgeTopLeft = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, sTlSize * 2, sTlSize * 2);
cairo_t* cr = cairo_create(this->edgeTopLeft);
drawShadowEdge(cr, 0, 0, sTlSize, sTlSize, reinterpret_cast<u8ptr>(shadowEdge1), 0, 0, 0);
cairo_destroy(cr);
this->edgeTopRight = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, sBrSize * 2, sTlSize * 2);
cr = cairo_create(this->edgeTopRight);
drawShadowEdge(cr, 0, 0, sBrSize, sTlSize, reinterpret_cast<u8ptr>(shadowEdge2), 0, 0, 0);
cairo_destroy(cr);
this->edgeBottomRight = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, sBrSize * 2, sBrSize * 2);
cr = cairo_create(this->edgeBottomRight);
drawShadowEdge(cr, 0, 0, sBrSize, sBrSize, reinterpret_cast<u8ptr>(shadowEdge3), 0, 0, 0);
cairo_destroy(cr);
this->edgeBottomLeft = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, sTlSize * 2, sBrSize * 2);
cr = cairo_create(this->edgeBottomLeft);
drawShadowEdge(cr, 0, 0, sTlSize, sBrSize, reinterpret_cast<u8ptr>(shadowEdge4), 0, 0, 0);
cairo_destroy(cr);
}
Shadow::~Shadow() {
cairo_surface_destroy(this->edgeBottomLeft);
cairo_surface_destroy(this->edgeBottomRight);
cairo_surface_destroy(this->edgeTopLeft);
cairo_surface_destroy(this->edgeTopRight);
this->edgeBottomLeft = nullptr;
this->edgeBottomRight = nullptr;
this->edgeTopLeft = nullptr;
this->edgeTopRight = nullptr;
if (this->left) {
cairo_surface_destroy(this->left);
}
if (this->top) {
cairo_surface_destroy(this->top);
}
if (this->right) {
cairo_surface_destroy(this->right);
}
if (this->bottom) {
cairo_surface_destroy(this->bottom);
}
this->left = nullptr;
this->top = nullptr;
this->right = nullptr;
this->bottom = nullptr;
}
auto Shadow::getShadowBottomRightSize() -> int { return shadowBottomRightSize; }
auto Shadow::getShadowTopLeftSize() -> int { return shadowTopLeftSize; }
void Shadow::drawShadowEdge(cairo_t* cr, int x, int y, int width, int height, const unsigned char* edge, double r,
double g, double b) {
int w = width * 2;
int h = height * 2;
for (int u = 0; u < h; u++) {
for (int i = 0; i < w; i++) {
double a = edge[u * w + i] / 255.0;
cairo_set_source_rgba(cr, r, g, b, a);
cairo_rectangle(cr, x + i, y + u, 1, 1);
cairo_fill(cr);
}
}
}
void Shadow::paintEdge(cairo_t* cr, cairo_surface_t* image, int x, int y, int width, int height) {
cairo_set_source_surface(cr, image, x - width, y - height);
cairo_rectangle(cr, x - width, y - height, width * 2, height * 2);
cairo_fill(cr);
}
static constexpr int PATTERN_LENGTH = 200; ///< Why not?
static void setPattern(cairo_t* cr, cairo_surface_t* patt, double offsetX, double offsetY) {
cairo_pattern_t* p = cairo_pattern_create_for_surface(patt);
cairo_pattern_set_extend(p, CAIRO_EXTEND_REPEAT);
cairo_matrix_t m;
cairo_matrix_init_translate(&m, -offsetX, -offsetY);
cairo_pattern_set_matrix(p, &m);
cairo_set_source(cr, p);
cairo_pattern_destroy(p);
}
void Shadow::drawShadowTop(cairo_t* cr, int x, int y, int width, double r, double g, double b) {
if (!this->top) {
this->top = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PATTERN_LENGTH, shadowTopLeftSize);
cairo_t* cr2 = cairo_create(this->top);
double a = NAN;
// draw shadow on top and left
for (int i = 0; i < shadowTopLeftSize; i++) {
a = shadowTopLeft[i] / 255.0;
cairo_set_source_rgba(cr2, r, g, b, a);
cairo_rectangle(cr2, 0, i, PATTERN_LENGTH, 1);
cairo_fill(cr2);
}
cairo_destroy(cr2);
}
setPattern(cr, this->top, x + shadowTopLeftSize, y - shadowTopLeftSize);
cairo_rectangle(cr, x + shadowTopLeftSize, y - shadowTopLeftSize, width - 2 * shadowTopLeftSize, shadowTopLeftSize);
cairo_fill(cr);
}
void Shadow::drawShadowLeft(cairo_t* cr, int x, int y, int height, double r, double g, double b) {
if (!this->left) {
this->left = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, shadowTopLeftSize, PATTERN_LENGTH);
cairo_t* cr2 = cairo_create(this->left);
double a = NAN;
// draw shadow on top and left
for (int i = 0; i < shadowTopLeftSize; i++) {
a = shadowTopLeft[i] / 255.0;
cairo_set_source_rgba(cr2, r, g, b, a);
cairo_rectangle(cr2, i, 0, 1, PATTERN_LENGTH);
cairo_fill(cr2);
}
cairo_destroy(cr2);
}
setPattern(cr, this->left, x - shadowTopLeftSize, y + shadowTopLeftSize);
cairo_rectangle(cr, x - shadowTopLeftSize, y + shadowTopLeftSize, shadowTopLeftSize,
height - 2 * shadowTopLeftSize);
cairo_fill(cr);
}
void Shadow::drawShadowRight(cairo_t* cr, int x, int y, int height, double r, double g, double b) {
if (!this->right) {
this->right = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, shadowBottomRightSize, PATTERN_LENGTH);
cairo_t* cr2 = cairo_create(this->right);
double a = NAN;
// draw shadow on top and left
for (int i = 0; i < shadowBottomRightSize; i++) {
a = shadowBottomRight[i] / 255.0;
cairo_set_source_rgba(cr2, r, g, b, a);
cairo_rectangle(cr2, i, 0, 1, PATTERN_LENGTH);
cairo_fill(cr2);
}
cairo_destroy(cr2);
}
setPattern(cr, this->right, x, y + shadowBottomRightSize);
cairo_rectangle(cr, x, y + shadowBottomRightSize, shadowBottomRightSize, height - 2 * shadowBottomRightSize);
cairo_fill(cr);
}
void Shadow::drawShadowBottom(cairo_t* cr, int x, int y, int width, double r, double g, double b) {
if (!this->bottom) {
this->bottom = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PATTERN_LENGTH, shadowBottomRightSize);
cairo_t* cr2 = cairo_create(this->bottom);
// draw shadow on top and left
for (int i = 0; i < shadowBottomRightSize; i++) {
auto a = shadowBottomRight[i] / 255.0;
cairo_set_source_rgba(cr2, r, g, b, a);
cairo_rectangle(cr2, 0, i, PATTERN_LENGTH, 1);
cairo_fill(cr2);
}
cairo_destroy(cr2);
}
setPattern(cr, this->bottom, x + shadowBottomRightSize, y);
cairo_rectangle(cr, x + shadowBottomRightSize, y, width - 2 * shadowBottomRightSize, shadowBottomRightSize);
cairo_fill(cr);
}
void Shadow::drawShadowImpl(cairo_t* cr, int x, int y, int width, int height) {
const int sBrSize = shadowBottomRightSize;
const int sTlSize = shadowTopLeftSize;
drawShadowTop(cr, x, y, width - 3, 0, 0, 0);
drawShadowLeft(cr, x, y, height - 3, 0, 0, 0);
drawShadowRight(cr, x + width, y - 4, height + 5, 0, 0, 0);
drawShadowBottom(cr, x - 4, y + height, width + 5, 0, 0, 0);
paintEdge(cr, this->edgeTopLeft, x, y, sTlSize, sTlSize);
paintEdge(cr, this->edgeTopRight, x + width + 1, y, sBrSize, sTlSize);
paintEdge(cr, this->edgeBottomLeft, x, y + height + 1, sTlSize, sBrSize);
paintEdge(cr, this->edgeBottomRight, x + width + 1, y + height + 1, sBrSize, sBrSize);
}
void Shadow::drawShadow(cairo_t* cr, int x, int y, int width, int height) {
Shadow::instance->drawShadowImpl(cr, x, y, width, height);
}
-52
View File
@@ -1,52 +0,0 @@
/*
* Xournal++
*
* Settings Dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <cairo.h> // for cairo_surface_t, cairo_t
class Shadow {
private:
Shadow();
virtual ~Shadow();
static void drawShadowEdge(cairo_t* cr, int x, int y, int width, int height, const unsigned char* edge, double r,
double g, double b);
void drawShadowImpl(cairo_t* cr, int x, int y, int width, int height);
static void paintEdge(cairo_t* cr, cairo_surface_t* image, int x, int y, int width, int height);
void drawShadowTop(cairo_t* cr, int x, int y, int width, double r, double g, double b);
void drawShadowLeft(cairo_t* cr, int x, int y, int height, double r, double g, double b);
void drawShadowRight(cairo_t* cr, int x, int y, int height, double r, double g, double b);
void drawShadowBottom(cairo_t* cr, int x, int y, int width, double r, double g, double b);
public:
/**
* This is the public interface of this class
*/
static void drawShadow(cairo_t* cr, int x, int y, int width, int height);
static int getShadowBottomRightSize();
static int getShadowTopLeftSize();
private:
static Shadow* instance;
cairo_surface_t* edgeTopLeft = nullptr;
cairo_surface_t* edgeTopRight = nullptr;
cairo_surface_t* edgeBottomLeft = nullptr;
cairo_surface_t* edgeBottomRight = nullptr;
cairo_surface_t* top = nullptr;
cairo_surface_t* right = nullptr;
cairo_surface_t* bottom = nullptr;
cairo_surface_t* left = nullptr;
};
@@ -1,99 +0,0 @@
///////////////////////////////////////////////
// Auto-generated from image source
const int shadowTopLeftSize = 8;
const int shadowBottomRightSize = 12;
const unsigned char shadowTopLeft[] = {0, 1, 3, 6, 12, 22, 36, 57};
const unsigned char shadowBottomRight[] = {171, 142, 111, 82, 56, 37, 22, 12, 6, 3, 1, 0};
const unsigned char shadowEdge1[16][16] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3},
{0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 3, 4, 5, 5, 5, 6},
{0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 11},
{0, 0, 0, 1, 1, 2, 3, 5, 7, 10, 12, 15, 17, 19, 20, 21},
{0, 0, 0, 1, 2, 3, 5, 8, 12, 16, 20, 24, 28, 31, 33, 34},
{0, 0, 1, 1, 3, 5, 8, 13, 18, 25, 32, 39, 44, 49, 52, 54},
{0, 0, 1, 2, 4, 7, 12, 18, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 3, 5, 9, 16, 25, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 1, 3, 7, 12, 20, 32, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 4, 8, 15, 25, 38, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 5, 9, 17, 28, 44, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 5, 10, 18, 31, 49, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 5, 11, 20, 33, 52, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 6, 11, 21, 35, 54, 0, 0, 0, 0, 0, 0, 0, 0},
};
const unsigned char shadowEdge2[16][24] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0},
{12, 12, 12, 12, 12, 12, 12, 11, 11, 10, 9, 8, 7, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0},
{22, 22, 22, 22, 22, 22, 21, 21, 20, 19, 17, 15, 12, 10, 7, 5, 3, 2, 1, 1, 0, 0, 0, 0},
{36, 36, 36, 36, 36, 36, 35, 34, 33, 31, 28, 24, 20, 16, 12, 8, 5, 3, 2, 1, 0, 0, 0, 0},
{57, 57, 57, 57, 57, 56, 56, 54, 52, 49, 44, 39, 32, 25, 18, 13, 8, 5, 3, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 46, 36, 27, 18, 12, 7, 4, 2, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, 63, 49, 36, 25, 16, 10, 5, 3, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97, 80, 63, 46, 32, 20, 12, 7, 3, 1, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116, 96, 76, 56, 38, 25, 15, 8, 4, 2, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 134, 111, 87, 64, 44, 28, 17, 9, 5, 2, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 122, 96, 71, 49, 31, 19, 10, 5, 2, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 131, 102, 75, 52, 33, 20, 11, 5, 2, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 136, 107, 79, 54, 35, 21, 11, 6, 3, 1, 0, 0},
};
const unsigned char shadowEdge3[24][24] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 143, 112, 83, 57, 37, 22, 12, 6, 3, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 143, 112, 83, 57, 37, 22, 12, 6, 3, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 143, 112, 83, 57, 37, 22, 12, 6, 3, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 143, 112, 83, 57, 37, 22, 12, 6, 3, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 142, 112, 82, 57, 36, 22, 12, 6, 3, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 141, 111, 82, 56, 36, 21, 12, 6, 3, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 168, 140, 109, 81, 56, 36, 21, 12, 6, 3, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 136, 107, 79, 54, 35, 21, 11, 6, 3, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 131, 102, 75, 52, 33, 20, 11, 5, 2, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 123, 96, 71, 49, 31, 19, 10, 5, 2, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 134, 111, 87, 64, 44, 28, 17, 9, 5, 2, 1, 0, 0},
{173, 173, 173, 173, 172, 171, 169, 165, 158, 148, 134, 117, 97, 76, 56, 39, 25, 15, 8, 4, 2, 1, 0, 0},
{143, 143, 143, 143, 142, 142, 140, 136, 131, 123, 111, 97, 80, 63, 46, 32, 20, 12, 7, 3, 1, 1, 0, 0},
{112, 112, 112, 112, 112, 111, 109, 107, 103, 96, 87, 76, 63, 49, 36, 25, 16, 10, 5, 3, 1, 0, 0, 0},
{83, 83, 83, 83, 83, 82, 81, 79, 76, 71, 65, 56, 47, 36, 27, 19, 12, 7, 4, 2, 1, 0, 0, 0},
{57, 57, 57, 57, 57, 56, 56, 54, 52, 49, 44, 39, 32, 25, 18, 13, 8, 5, 3, 1, 1, 0, 0, 0},
{37, 37, 37, 37, 37, 37, 36, 35, 34, 32, 29, 25, 21, 16, 12, 8, 5, 3, 2, 1, 0, 0, 0, 0},
{22, 22, 22, 22, 22, 22, 21, 21, 20, 19, 17, 15, 12, 10, 7, 5, 3, 2, 1, 1, 0, 0, 0, 0},
{12, 12, 12, 12, 12, 12, 12, 11, 11, 10, 9, 8, 7, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0},
{6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0},
{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
};
const unsigned char shadowEdge4[24][16] = {
{0, 1, 3, 6, 12, 22, 36, 57, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 3, 6, 12, 22, 36, 57, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 3, 6, 12, 22, 36, 57, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 3, 6, 12, 22, 36, 57, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 3, 6, 12, 22, 36, 57, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 3, 6, 12, 21, 36, 56, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 6, 11, 21, 36, 55, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 6, 11, 21, 35, 54, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 5, 11, 20, 33, 52, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 5, 10, 19, 31, 49, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 5, 9, 17, 28, 44, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 4, 8, 15, 25, 39, 56, 76, 97, 117, 134, 148, 158, 165},
{0, 1, 1, 3, 7, 12, 20, 32, 46, 63, 80, 97, 111, 123, 131, 136},
{0, 0, 1, 3, 5, 9, 16, 25, 36, 49, 63, 76, 87, 96, 102, 107},
{0, 0, 1, 2, 4, 7, 12, 18, 27, 36, 47, 56, 64, 71, 76, 79},
{0, 0, 1, 1, 3, 5, 8, 13, 18, 25, 32, 39, 44, 49, 52, 54},
{0, 0, 0, 1, 2, 3, 5, 8, 12, 16, 21, 25, 29, 32, 34, 35},
{0, 0, 0, 1, 1, 2, 3, 5, 7, 10, 12, 15, 17, 19, 20, 21},
{0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 11},
{0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 3, 4, 5, 5, 5, 6},
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
};
///////////////////////////////////////////////
@@ -1,44 +0,0 @@
/*
* Xournal++
*
* The definitions for the possibel toolbars
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
typedef struct {
/**
* The name in the glade.xml file
*/
const char* guiName;
/**
* The name in the .ini file
*/
const char* propName;
/**
* if horizontal (true) or vertical (false)
*/
bool horizontal;
} ToolbarEntryDefintion;
const static ToolbarEntryDefintion TOOLBAR_DEFINITIONS[] = {
{"tbTop1", "toolbarTop1", true}, {"tbTop2", "toolbarTop2", true},
{"tbLeft1", "toolbarLeft1", false}, {"tbLeft2", "toolbarLeft2", false},
{"tbRight1", "toolbarRight1", false}, {"tbRight2", "toolbarRight2", false},
{"tbBottom1", "toolbarBottom1", true}, {"tbBottom2", "toolbarBottom2", true},
{"tbFloat1", "toolbarFloat1", true}, // define this index below as TBFloatFirst
{"tbFloat2", "toolbarFloat2", true}, {"tbFloat3", "toolbarFloat3", true},
{"tbFloat4", "toolbarFloat4", true} // define this index below as TBFloatLast
};
const static int TOOLBAR_DEFINITIONS_LEN = G_N_ELEMENTS(TOOLBAR_DEFINITIONS);
#define TBFloatFirst 8
#define TBFloatLast 11
@@ -1,88 +0,0 @@
#include "ToolitemDragDrop.h"
#include <glib-object.h> // for g_object_...
#include <glib.h> // for g_malloc
#include "gui/toolbarMenubar/AbstractToolItem.h" // for AbstractT...
#include "gui/toolbarMenubar/icon/ToolbarSeparatorImage.h" // for getNewToo...
struct NamedColor;
const char* ATTACH_DRAG_DROP_DATA = "XOJ_DRAG_DROP_DATA";
void ToolitemDragDrop::attachMetadata(GtkWidget* w, int id, AbstractToolItem* ait) {
ToolItemDragDropData* d = g_new(ToolItemDragDropData, 1);
d->identify = ToolItemDragDropData_Identify;
d->id = id;
d->item = ait;
d->type = TOOL_ITEM_ITEM;
d->paletteColorIndex = 0;
g_object_set_data_full(G_OBJECT(w), ATTACH_DRAG_DROP_DATA, d, static_cast<GDestroyNotify>(g_free));
}
auto ToolitemDragDrop::ToolItemDragDropData_new(AbstractToolItem* item) -> std::unique_ptr<ToolItemDragDropData> {
auto d = std::make_unique<ToolItemDragDropData>();
d->identify = ToolItemDragDropData_Identify;
d->id = -1;
d->item = item;
d->type = TOOL_ITEM_ITEM;
d->paletteColorIndex = 0;
return d;
}
void ToolitemDragDrop::attachMetadata(GtkWidget* w, int id, ToolItemType type) {
ToolItemDragDropData* d = g_new(ToolItemDragDropData, 1);
d->identify = ToolItemDragDropData_Identify;
d->id = id;
d->item = nullptr;
d->type = type;
d->paletteColorIndex = 0;
g_object_set_data_full(G_OBJECT(w), ATTACH_DRAG_DROP_DATA, d, static_cast<GDestroyNotify>(g_free));
}
void ToolitemDragDrop::attachMetadataColor(GtkWidget* w, int id, size_t paletteColorIndex, AbstractToolItem* item) {
ToolItemDragDropData* d = g_new(ToolItemDragDropData, 1);
d->identify = ToolItemDragDropData_Identify;
d->id = id;
d->item = item;
d->type = TOOL_ITEM_COLOR;
d->paletteColorIndex = paletteColorIndex;
g_object_set_data_full(G_OBJECT(w), ATTACH_DRAG_DROP_DATA, d, static_cast<GDestroyNotify>(g_free));
}
auto ToolitemDragDrop::getIcon(ToolItemDragDropData* data) -> GtkWidget* {
if (data->type == TOOL_ITEM_ITEM || data->type == TOOL_ITEM_COLOR) {
return data->item->getNewToolIcon();
}
if (data->type == TOOL_ITEM_SEPARATOR) {
return ToolbarSeparatorImage::newImage(SeparatorType::SEPARATOR);
}
if (data->type == TOOL_ITEM_SPACER) {
return ToolbarSeparatorImage::newImage(SeparatorType::SPACER);
}
g_warning("ToolitemDragDrop::getIcon unhandled type: %i\n", data->type);
return gtk_image_new();
}
auto ToolitemDragDrop::checkToolItemDragDropData(ToolItemDragDropData const* d) -> bool {
return d->identify == ToolItemDragDropData_Identify;
}
auto ToolitemDragDrop::metadataGetMetadata(GtkWidget* w) -> ToolItemDragDropData* {
void* ptr = g_object_get_data(G_OBJECT(w), ATTACH_DRAG_DROP_DATA);
if (ptr == nullptr) {
g_warning("ToolitemDragDrop::metadataGetMetadata Could not get Metadata %s from %s\n", ATTACH_DRAG_DROP_DATA,
g_type_name(G_TYPE_FROM_INSTANCE(w)));
return nullptr;
}
if (!checkToolItemDragDropData(static_cast<ToolItemDragDropData const*>(ptr))) {
g_warning("ToolitemDragDrop::metadataGetMetadata data is not an instance of ToolItemDragDropData!");
return nullptr;
}
return static_cast<ToolItemDragDropData*>(ptr);
}
@@ -1,57 +0,0 @@
/*
* Xournal++
*
* Helper for Toolbar Drag & Drop implementation
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <memory>
#include <gdk-pixbuf/gdk-pixbuf.h> // for GdkPixbuf
#include <gtk/gtk.h> // for GtkWidget
class AbstractToolItem;
struct NamedColor;
enum ToolItemType { TOOL_ITEM_SEPARATOR = 0, TOOL_ITEM_SPACER, TOOL_ITEM_ITEM, TOOL_ITEM_COLOR };
#define ToolItemDragDropData_Identify 0xFA090201
struct ToolItemDragDropData {
unsigned int identify;
ToolItemType type;
int id;
AbstractToolItem* item;
size_t paletteColorIndex;
};
class ToolitemDragDrop {
private:
ToolitemDragDrop();
virtual ~ToolitemDragDrop();
public:
static void attachMetadata(GtkWidget* w, int id, AbstractToolItem* ait);
static void attachMetadata(GtkWidget* w, int id, ToolItemType type);
static void attachMetadataColor(GtkWidget* w, int id, size_t paletteColorIndex, AbstractToolItem* item);
public:
static std::unique_ptr<ToolItemDragDropData> ToolItemDragDropData_new(AbstractToolItem* item);
static bool checkToolItemDragDropData(ToolItemDragDropData const* d);
static ToolItemDragDropData* metadataGetMetadata(GtkWidget* w);
/**
* Returns: (transfer floating)
*/
static GtkWidget* getIcon(ToolItemDragDropData* data);
public:
static void removeFromToolbarForMove(GtkWidget* widget);
};
@@ -1,861 +0,0 @@
#include "XournalView.h"
#include <algorithm> // for max, min
#include <iterator> // for begin
#include <memory> // for unique_ptr, make_unique
#include <optional> // for optional
#include <unordered_set>
#include <gdk/gdk.h> // for GdkEventKey, GDK_SHIF...
#include <gdk/gdkkeysyms.h> // for GDK_KEY_Page_Down
#include <glib-object.h> // for g_object_ref_sink
#include "control/Control.h" // for Control
#include "control/PdfCache.h" // for PdfCache
#include "control/ScrollHandler.h" // for ScrollHandler
#include "control/ToolHandler.h" // for ToolHandler
#include "control/actions/ActionDatabase.h" // for ActionDatabase
#include "control/jobs/XournalScheduler.h" // for XournalScheduler
#include "control/settings/MetadataManager.h" // for MetadataManager
#include "control/settings/Settings.h" // for Settings
#include "control/tools/CursorSelectionType.h" // for CURSOR_SELECTION_NONE
#include "control/tools/EditSelection.h" // for EditSelection
#include "control/zoom/ZoomControl.h" // for ZoomControl
#include "gui/MainWindow.h" // for MainWindow
#include "gui/PdfFloatingToolbox.h" // for PdfFloatingToolbox
#include "gui/inputdevices/GeometryToolInputHandler.h" // for GeometryToolInputHandler
#include "gui/inputdevices/HandRecognition.h" // for HandRecognition
#include "gui/inputdevices/InputContext.h" // for InputContext
#include "gui/toolbarMenubar/ColorToolItem.h" // for ColorToolItem
#include "gui/toolbarMenubar/ToolMenuHandler.h" // for ToolMenuHandler
#include "gui/widgets/XournalWidget.h" // for gtk_xournal_get_layout
#include "model/Document.h" // for Document
#include "model/Element.h" // for Element, ELEMENT_STROKE
#include "model/PageRef.h" // for PageRef
#include "model/Stroke.h" // for Stroke, StrokeTool::E...
#include "model/XojPage.h" // for XojPage
#include "undo/DeleteUndoAction.h" // for DeleteUndoAction
#include "undo/UndoRedoHandler.h" // for UndoRedoHandler
#include "util/Assert.h" // for xoj_assert
#include "util/Point.h" // for Point
#include "util/Rectangle.h" // for Rectangle
#include "util/Util.h" // for npos
#include "util/glib_casts.h" // for wrap_v
#include "util/gtk4_helper.h" // for gtk_scrolled_window_set_child
#include "util/safe_casts.h" // for round_cast
#include "Layout.h" // for Layout
#include "PageView.h" // for XojPageView
#include "RepaintHandler.h" // for RepaintHandler
#include "XournalppCursor.h" // for XournalppCursor
using xoj::util::Rectangle;
constexpr int REGULAR_MOVE_AMOUNT = 3;
constexpr int SMALL_MOVE_AMOUNT = 1;
constexpr int LARGE_MOVE_AMOUNT = 10;
std::pair<size_t, size_t> XournalView::preloadPageBounds(size_t page, size_t maxPage) {
const size_t preloadBefore = this->control->getSettings()->getPreloadPagesBefore();
const size_t preloadAfter = this->control->getSettings()->getPreloadPagesAfter();
const size_t lower = page > preloadBefore ? page - preloadBefore : 0;
const size_t upper = std::min(maxPage, page + preloadAfter + 1);
return {lower, upper};
}
XournalView::XournalView(GtkWidget* parent, Control* control, ScrollHandling* scrollHandling):
scrollHandling(scrollHandling), control(control) {
Document* doc = control->getDocument();
doc->lock_shared();
if (doc->getPdfPageCount() != 0) {
this->cache = std::make_unique<PdfCache>(doc->getPdfDocument(), control->getSettings());
}
doc->unlock_shared();
registerListener(control);
InputContext* inputContext = new InputContext(this, scrollHandling);
this->widget = gtk_xournal_new(this, inputContext);
g_object_ref_sink(this->widget); // take ownership without increasing the ref count
gtk_container_add(GTK_CONTAINER(parent), this->widget);
gtk_widget_show(this->widget);
g_signal_connect(getWidget(), "realize", G_CALLBACK(+[](GtkWidget* widget, gpointer) {
// Disable event compression
if (gtk_widget_get_realized(widget)) {
gdk_window_set_event_compression(gtk_widget_get_window(widget), false);
} else {
g_warning("could not disable event compression");
}
}),
nullptr);
this->repaintHandler = std::make_unique<RepaintHandler>(this);
control->getZoomControl()->addZoomListener(this);
gtk_widget_set_can_default(this->widget, true);
gtk_widget_grab_default(this->widget);
gtk_widget_grab_focus(this->widget);
this->cleanupTimeout = g_timeout_add_seconds(5, xoj::util::wrap_v<clearMemoryTimer>, this);
}
XournalView::~XournalView() {
g_source_remove(this->cleanupTimeout);
gtk_widget_destroy(this->widget);
this->widget = nullptr;
}
auto XournalView::clearMemoryTimer(XournalView* widget) -> gboolean {
widget->cleanupBufferCache();
return G_SOURCE_CONTINUE;
}
auto XournalView::cleanupBufferCache() -> void {
const auto& [pagesLower, pagesUpper] = this->preloadPageBounds(this->currentPage, this->viewPages.size());
xoj_assert(pagesLower <= pagesUpper);
std::unordered_set<size_t> retainedPdfPages;
for (size_t i = 0; i < this->viewPages.size(); i++) {
auto&& page = this->viewPages[i];
const bool isPreload = pagesLower <= i && i < pagesUpper;
const bool shouldRetain = isPreload || page->isVisible();
if (shouldRetain) {
const size_t pdfPageNo = page->getPage()->getPdfPageNr();
if (pdfPageNo != npos) {
retainedPdfPages.insert(pdfPageNo);
}
continue;
} else if (page->hasBuffer()) {
page->deleteViewBuffer();
}
}
if (this->cache) {
this->cache->evictAllExcept(retainedPdfPages);
}
}
auto XournalView::getCurrentPage() const -> size_t { return currentPage; }
const int scrollKeySize = 30;
auto XournalView::onKeyPressEvent(const KeyEvent& event) -> bool {
size_t p = getCurrentPage();
if (p != npos && p < this->viewPages.size()) {
auto& v = this->viewPages[p];
if (v->onKeyPressEvent(event)) {
return true;
}
}
auto keyval = event.keyval;
auto state = event.state;
if (auto* tool = getControl()->getWindow()->getPdfToolbox(); tool->hasSelection()) {
if ((keyval == GDK_KEY_c && state == GDK_CONTROL_MASK) || keyval == GDK_KEY_Copy) {
// Shortcut to get selected PDF text.
tool->copyTextToClipboard();
return true;
}
}
if (auto* selection = getSelection(); selection) {
if (keyval == GDK_KEY_Escape) {
clearSelection();
return true;
}
int d = REGULAR_MOVE_AMOUNT;
if (state == GDK_MOD1_MASK) {
d = SMALL_MOVE_AMOUNT;
} else if (state == GDK_SHIFT_MASK) {
d = LARGE_MOVE_AMOUNT;
}
int xdir = 0;
int ydir = 0;
if (keyval == GDK_KEY_Left) {
xdir = -1;
} else if (keyval == GDK_KEY_Up) {
ydir = -1;
} else if (keyval == GDK_KEY_Right) {
xdir = 1;
} else if (keyval == GDK_KEY_Down) {
ydir = 1;
}
if (xdir != 0 || ydir != 0) {
selection->moveSelection(d * xdir, d * ydir, /*addMoveUndo=*/true);
selection->ensureWithinVisibleArea();
return true;
}
}
if (auto* geom = GTK_XOURNAL(widget)->input->getGeometryToolInputHandler(); geom && geom->keyPressed(event)) {
return true;
}
Layout* layout = this->getLayout();
if (!state) {
if (keyval == GDK_KEY_Page_Down || keyval == GDK_KEY_KP_Page_Down) {
control->getScrollHandler()->goToNextPage();
return true;
}
if (keyval == GDK_KEY_Page_Up || keyval == GDK_KEY_KP_Page_Up) {
control->getScrollHandler()->goToPreviousPage();
return true;
}
}
if (keyval == GDK_KEY_space) {
GtkAllocation alloc = {0};
gtk_widget_get_allocation(gtk_widget_get_parent(this->widget), &alloc);
int windowHeight = alloc.height - scrollKeySize;
if (!state) {
layout->scrollRelative(0, windowHeight);
return true;
}
if (state == GDK_SHIFT_MASK) {
layout->scrollRelative(0, -windowHeight);
return true;
}
}
// Numeric keypad always navigates by page
if (keyval == GDK_KEY_KP_Up) {
this->pageRelativeXY(0, -1);
return true;
}
if (keyval == GDK_KEY_KP_Down) {
this->pageRelativeXY(0, 1);
return true;
}
if (keyval == GDK_KEY_KP_Left) {
this->pageRelativeXY(-1, 0);
return true;
}
if (keyval == GDK_KEY_KP_Right) {
this->pageRelativeXY(1, 0);
return true;
}
if (keyval == GDK_KEY_Up || keyval == GDK_KEY_k || keyval == GDK_KEY_K) {
if (control->getSettings()->isPresentationMode()) {
control->getScrollHandler()->goToPreviousPage();
return true;
}
if (state == GDK_SHIFT_MASK) {
this->pageRelativeXY(0, -1);
return true;
}
if (!state) {
layout->scrollRelative(0, -scrollKeySize);
return true;
}
}
if (keyval == GDK_KEY_Down || keyval == GDK_KEY_j || keyval == GDK_KEY_J) {
if (control->getSettings()->isPresentationMode()) {
control->getScrollHandler()->goToNextPage();
return true;
}
if (state == GDK_SHIFT_MASK) {
this->pageRelativeXY(0, 1);
return true;
}
if (!state) {
layout->scrollRelative(0, scrollKeySize);
return true;
}
}
if (keyval == GDK_KEY_Left || keyval == GDK_KEY_h) {
if (state == GDK_SHIFT_MASK) {
this->pageRelativeXY(-1, 0);
return true;
}
if (!state) {
if (control->getSettings()->isPresentationMode()) {
control->getScrollHandler()->goToPreviousPage();
} else {
layout->scrollRelative(-scrollKeySize, 0);
}
return true;
}
}
if (keyval == GDK_KEY_Right || keyval == GDK_KEY_l) {
if (state == GDK_SHIFT_MASK) {
this->pageRelativeXY(1, 0);
return true;
}
if (!state) {
if (control->getSettings()->isPresentationMode()) {
control->getScrollHandler()->goToNextPage();
} else {
layout->scrollRelative(scrollKeySize, 0);
}
return true;
}
}
if (keyval == GDK_KEY_End || keyval == GDK_KEY_KP_End) {
control->getScrollHandler()->goToLastPage();
return true;
}
if (keyval == GDK_KEY_Home || keyval == GDK_KEY_KP_Home) {
control->getScrollHandler()->goToFirstPage();
return true;
}
// Switch color on number key
auto& colors = control->getWindow()->getToolMenuHandler()->getColorToolItems();
if (!state && (keyval >= GDK_KEY_0) && (keyval < GDK_KEY_0 + std::min((std::size_t)10, colors.size()))) {
std::size_t index = std::min(colors.size() - 1, (std::size_t)(9 + (keyval - GDK_KEY_0)) % 10);
const auto& colorToolItem = colors.at(index);
if (auto* db = control->getActionDatabase(); db->isActionEnabled(Action::TOOL_COLOR)) {
control->getActionDatabase()->fireChangeActionState(Action::TOOL_COLOR, colorToolItem->getColor());
}
return true;
}
return false;
}
auto XournalView::getRepaintHandler() const -> RepaintHandler* { return this->repaintHandler.get(); }
auto XournalView::onKeyReleaseEvent(const KeyEvent& event) -> bool {
size_t p = getCurrentPage();
if (p != npos && p < this->viewPages.size()) {
auto& v = this->viewPages[p];
if (v->onKeyReleaseEvent(event)) {
return true;
}
}
return false;
}
void XournalView::onSettingsChanged() {
if (this->cache) {
this->cache->updateSettings(control->getSettings());
}
}
// send the focus back to the appropriate widget
void XournalView::requestFocus() { gtk_widget_grab_focus(this->widget); }
auto XournalView::searchTextOnPage(const std::string& text, size_t pageNumber, size_t index, size_t* occurrences,
XojPdfRectangle* matchRect) -> bool {
if (pageNumber == npos || pageNumber >= this->viewPages.size()) {
return false;
}
auto& v = this->viewPages[pageNumber];
return v->searchTextOnPage(text, index, occurrences, matchRect);
}
void XournalView::forceUpdatePagenumbers() {
size_t p = this->currentPage;
this->currentPage = npos;
control->firePageSelected(p);
}
auto XournalView::getViewFor(size_t pageNr) const -> XojPageView* {
if (pageNr == npos || pageNr >= this->viewPages.size()) {
return nullptr;
}
return this->viewPages[pageNr].get();
}
void XournalView::pageSelected(size_t page) {
if (this->currentPage == page && this->lastSelectedPage == page) {
return;
}
control->getWindow()->getPdfToolbox()->userCancelSelection();
if (this->lastSelectedPage != npos && this->lastSelectedPage < this->viewPages.size()) {
this->viewPages[this->lastSelectedPage]->setSelected(false);
}
endTextAllPages();
this->currentPage = page;
size_t pdfPage = npos;
if (page != npos && page < viewPages.size()) {
auto& vp = viewPages[page];
vp->setSelected(true);
lastSelectedPage = page;
pdfPage = vp->getPage()->getPdfPageNr();
}
control->updatePageNumbers(currentPage, pdfPage);
control->updateBackgroundSizeButton();
control->updatePageActions();
if (control->getSettings()->isEagerPageCleanup()) {
this->cleanupBufferCache();
}
// Load surrounding pages if they are not
const auto& [pagesLower, pagesUpper] = preloadPageBounds(page, this->viewPages.size());
xoj_assert(pagesLower <= pagesUpper);
for (size_t i = pagesLower; i < pagesUpper; i++) {
if (!this->viewPages[i]->hasBuffer()) {
this->viewPages[i]->rerenderPage();
}
}
}
auto XournalView::getControl() const -> Control* { return control; }
void XournalView::scrollTo(size_t pageNo, XojPdfRectangle rect) {
double zoom = getZoom();
if (pageNo >= this->viewPages.size()) {
return;
}
auto& v = this->viewPages[pageNo];
// Make sure it is visible
Layout* layout = this->getLayout();
int x = v->getX() + round_cast<int>(rect.x1 * zoom);
int y = v->getY() + round_cast<int>(rect.y1 * zoom);
int width;
int height;
if (rect.x2 == -1 || rect.y2 == -1) {
width = v->getDisplayWidth();
height = v->getDisplayHeight();
} else {
width = round_cast<int>((rect.x2 - rect.x1) * zoom);
height = round_cast<int>((rect.y2 - rect.y1) * zoom);
}
layout->ensureRectIsVisible(x, y, width, height);
// Select the page
control->firePageSelected(pageNo);
}
void XournalView::pageRelativeXY(int offCol, int offRow) {
size_t currPage = getCurrentPage();
XojPageView* view = getViewFor(currPage);
int row = view->getMappedRow();
int col = view->getMappedCol();
Layout* layout = this->getLayout();
auto optionalPageIndex = layout->getPageIndexAtGridMap(as_unsigned(row + offRow), as_unsigned(col + offCol));
if (optionalPageIndex) {
this->scrollTo(*optionalPageIndex);
}
}
void XournalView::endTextAllPages(XojPageView* except) const {
for (auto& v: this->viewPages) {
if (except != v.get()) {
v->endText();
}
}
}
void XournalView::endSplineAllPages() const {
for (auto& v: this->viewPages) {
v->endSpline();
}
}
void XournalView::layerChanged(size_t page) {
if (page != npos && page < this->viewPages.size()) {
this->viewPages[page]->rerenderPage();
}
}
void XournalView::getPasteTarget(double& x, double& y) const {
size_t pageNo = getCurrentPage();
if (pageNo == npos) {
return;
}
Rectangle<double>* rect = getVisibleRect(pageNo);
if (rect) {
x = rect->x + rect->width / 2;
y = rect->y + rect->height / 2;
delete rect;
}
}
/**
* Return the rectangle which is visible on screen, in document cooordinates
*
* Or nullptr if the page is not visible
*/
auto XournalView::getVisibleRect(size_t page) const -> Rectangle<double>* {
if (page == npos || page >= this->viewPages.size()) {
return nullptr;
}
auto& p = this->viewPages[page];
return getVisibleRect(p.get());
}
auto XournalView::getVisibleRect(const XojPageView* redrawable) const -> Rectangle<double>* {
return gtk_xournal_get_visible_area(this->widget, redrawable);
}
void XournalView::recreatePdfCache() {
this->cache.reset();
Document* doc = control->getDocument();
doc->lock_shared();
if (doc->getPdfPageCount() != 0) {
this->cache = std::make_unique<PdfCache>(doc->getPdfDocument(), control->getSettings());
}
doc->unlock_shared();
}
/**
* @return Helper class for Touch specific fixes
*/
auto XournalView::getHandRecognition() const -> HandRecognition* {
return GTK_XOURNAL(widget)->input->getHandRecognition();
}
/**
* @return Scrollbars
*/
auto XournalView::getScrollHandling() const -> ScrollHandling* { return scrollHandling; }
auto XournalView::getWidget() const -> GtkWidget* { return widget; }
void XournalView::ensureRectIsVisible(int x, int y, int width, int height) {
Layout* layout = this->getLayout();
layout->ensureRectIsVisible(x, y, width, height);
}
void XournalView::zoomChanged() {
size_t currentPage = this->getCurrentPage();
XojPageView* view = getViewFor(currentPage);
ZoomControl* zoom = control->getZoomControl();
if (!view) {
return;
}
layoutPages();
if (zoom->isZoomPresentationMode()) {
scrollTo(currentPage);
} else if (zoom->isZoomSequenceActive()) {
auto pos = zoom->getScrollPositionAfterZoom();
Layout* layout = this->getLayout();
layout->scrollAbs(pos.x, pos.y);
}
// Updates the Eraser's cursor icon in order to make it as big as the erasing area
control->getCursor()->updateCursor();
// if we changed the zoom of the page, we should hide the pdf floating toolbox
// and if user clicked the selection again, the floating toolbox shows again
control->getWindow()->getPdfToolbox()->hide();
this->control->getScheduler()->blockRerenderZoom();
}
void XournalView::pageSizeChanged(size_t page) {
layoutPages();
if (page != npos && page < this->viewPages.size()) {
this->viewPages[page]->rerenderPage(/* sizeChanged */ true);
}
}
void XournalView::pageChanged(size_t page) {
if (page != npos && page < this->viewPages.size()) {
this->viewPages[page]->rerenderPage();
}
}
void XournalView::pageDeleted(size_t page) {
const size_t currentPageNo = control->getCurrentPageNo();
viewPages.erase(begin(viewPages) + static_cast<long>(page));
layoutPages();
if (currentPageNo > page) {
control->getScrollHandler()->scrollToPage(currentPageNo - 1);
} else {
control->getScrollHandler()->scrollToPage(currentPageNo);
}
}
auto XournalView::getTextEditor() const -> TextEditor* {
for (auto&& page: viewPages) {
if (page->getTextEditor()) {
return page->getTextEditor();
}
}
return nullptr;
}
auto XournalView::getCache() const -> PdfCache* { return this->cache.get(); }
void XournalView::pageInserted(size_t page) {
Document* doc = control->getDocument();
doc->lock_shared();
auto pageView = std::make_unique<XojPageView>(this, doc->getPage(page));
doc->unlock_shared();
viewPages.insert(begin(viewPages) + as_signed(page), std::move(pageView));
layoutPages();
// check which pages are visible and select the most visible page
Layout* layout = this->getLayout();
layout->updateVisibility();
}
auto XournalView::getZoom() const -> double { return control->getZoomControl()->getZoom(); }
auto XournalView::getDpiScaleFactor() const -> int { return gtk_widget_get_scale_factor(widget); }
void XournalView::clearSelection() {
EditSelection* sel = GTK_XOURNAL(widget)->selection;
GTK_XOURNAL(widget)->selection = nullptr;
delete sel;
control->setClipboardHandlerSelection(getSelection());
getCursor()->setMouseSelectionType(CURSOR_SELECTION_NONE);
control->getToolHandler()->setSelectionEditTools(false, false, false, false);
}
void XournalView::deleteSelection(EditSelection* sel) {
if (sel == nullptr) {
sel = getSelection();
}
if (sel) {
auto undo = std::make_unique<DeleteUndoAction>(sel->getSourcePage(), false);
sel->fillUndoItem(undo.get());
control->getUndoRedoHandler()->addUndoAction(std::move(undo));
clearSelection();
repaintSelection(true);
}
}
void XournalView::setSelection(EditSelection* selection) {
clearSelection();
GTK_XOURNAL(this->widget)->selection = selection;
control->setClipboardHandlerSelection(getSelection());
bool canChangeSize = false;
bool canChangeColor = false;
bool canChangeFill = false;
bool canChangeLineStyle = false;
for (const Element* e: selection->getElementsView()) {
switch (e->getType()) {
case ELEMENT_TEXT:
canChangeColor = true;
continue;
case ELEMENT_STROKE: {
canChangeSize = true;
const auto* s = dynamic_cast<const Stroke*>(e);
if (s->getToolType() == StrokeTool::PEN) {
// can change everything, leave loop with break
canChangeColor = true;
canChangeFill = true;
canChangeLineStyle = true;
break;
}
if (s->getToolType() == StrokeTool::HIGHLIGHTER) {
canChangeColor = true;
canChangeFill = true;
}
continue;
}
default:
continue;
}
// leave loop
break;
}
control->getToolHandler()->setSelectionEditTools(canChangeColor, canChangeSize, canChangeFill, canChangeLineStyle);
repaintSelection();
}
void XournalView::repaintSelection(bool evenWithoutSelection) {
if (evenWithoutSelection) {
gtk_widget_queue_draw(this->widget);
return;
}
EditSelection* selection = getSelection();
if (selection == nullptr) {
return;
}
// repaint always the whole widget
gtk_widget_queue_draw(this->widget);
}
void XournalView::layoutPages() {
Layout* layout = this->getLayout();
layout->recalculate();
// Todo (fabian): the following lines are conceptually wrong, the Layout::layoutPages function is meant to be
// called by an expose event, but removing it, will break "add page".
auto rectangle = layout->getVisibleRect();
layout->layoutPages(std::max<int>(layout->getMinimalWidth(), round_cast<int>(rectangle.width)),
std::max<int>(layout->getMinimalHeight(), round_cast<int>(rectangle.height)));
}
auto XournalView::getDisplayHeight() const -> int {
GtkAllocation allocation = {0};
gtk_widget_get_allocation(this->widget, &allocation);
return allocation.height;
}
auto XournalView::getDisplayWidth() const -> int {
GtkAllocation allocation = {0};
gtk_widget_get_allocation(this->widget, &allocation);
return allocation.width;
}
auto XournalView::isPageVisible(size_t page, int* visibleHeight) const -> bool {
Rectangle<double>* rect = getVisibleRect(page);
if (rect) {
if (visibleHeight) {
*visibleHeight = round_cast<int>(rect->height);
}
delete rect;
return true;
}
if (visibleHeight) {
*visibleHeight = 0;
}
return false;
}
void XournalView::documentChanged(DocumentChangeType type) {
if (type != DOCUMENT_CHANGE_CLEARED && type != DOCUMENT_CHANGE_COMPLETE) {
return;
}
XournalScheduler* scheduler = this->control->getScheduler();
scheduler->lock();
scheduler->removeAllJobs();
clearSelection();
recreatePdfCache();
Document* doc = control->getDocument();
doc->lock_shared();
viewPages.clear();
size_t pagecount = doc->getPageCount();
viewPages.reserve(pagecount);
for (size_t i = 0; i < pagecount; i++) {
viewPages.emplace_back(std::make_unique<XojPageView>(this, doc->getPage(i)));
}
doc->unlock_shared();
layoutPages();
scrollTo(0);
scheduler->unlock();
}
auto XournalView::cut() -> bool {
size_t p = getCurrentPage();
if (p == npos || p >= viewPages.size()) {
return false;
}
auto& page = viewPages[p];
return page->cut();
}
auto XournalView::copy() -> bool {
size_t p = getCurrentPage();
if (p == npos || p >= viewPages.size()) {
return false;
}
auto& page = viewPages[p];
return page->copy();
}
auto XournalView::paste() -> bool {
size_t p = getCurrentPage();
if (p == npos || p >= viewPages.size()) {
return false;
}
auto& page = viewPages[p];
return page->paste();
}
auto XournalView::actionDelete() -> bool {
size_t p = getCurrentPage();
if (p == npos || p >= viewPages.size()) {
return false;
}
auto& page = viewPages[p];
return page->actionDelete();
}
auto XournalView::getDocument() const -> Document* { return control->getDocument(); }
auto XournalView::getViewPages() const -> std::vector<std::unique_ptr<XojPageView>> const& { return viewPages; }
auto XournalView::getCursor() const -> XournalppCursor* { return control->getCursor(); }
auto XournalView::getSelection() const -> EditSelection* {
g_return_val_if_fail(this->widget != nullptr, nullptr);
g_return_val_if_fail(GTK_IS_XOURNAL(this->widget), nullptr);
return GTK_XOURNAL(this->widget)->selection;
}
auto XournalView::getLayout() const -> Layout* { return gtk_xournal_get_layout(getWidget()); }
@@ -1,195 +0,0 @@
/*
* Xournal++
*
* The widget which displays the PDF and the drawings
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <cstddef> // for size_t
#include <limits> // for numeric_limits
#include <memory> // for unique_ptr
#include <string> // for string
#include <utility> // for pair
#include <vector> // for vector
#include <gdk/gdk.h> // for GdkEventKey, GdkEventExpose
#include <glib.h> // for gboolean
#include <gtk/gtk.h> // for GtkWidget, GtkAllocation
#include "control/zoom/ZoomListener.h" // for ZoomListener
#include "gui/inputdevices/InputEvents.h" // for KeyEvent
#include "model/DocumentChangeType.h" // for DocumentChangeType
#include "model/DocumentListener.h" // for DocumentListener
#include "pdf/base/XojPdfPage.h" // for XojPdfRectangle
#include "util/Util.h" // for npos
class Control;
class XournalppCursor;
class Document;
class EditSelection;
class XojPageView;
class XojPdfRectangle;
class PdfCache;
class RepaintHandler;
class ScrollHandling;
class TextEditor;
class HandRecognition;
class Layout;
namespace xoj::util {
template <class T>
class Rectangle;
} // namespace xoj::util
class XournalView: public DocumentListener, public ZoomListener {
public:
XournalView(GtkWidget* parent, Control* control, ScrollHandling* scrollHandling);
~XournalView() override;
public:
// Recalculate the layout width and height amd layout the pages with the updated layout size
void layoutPages();
void scrollTo(size_t pageNo, XojPdfRectangle rect = {0, 0, -1, -1});
// Relative navigation in current layout:
void pageRelativeXY(int offCol, int offRow);
size_t getCurrentPage() const;
void clearSelection();
void layerChanged(size_t page);
void requestFocus();
void forceUpdatePagenumbers();
XojPageView* getViewFor(size_t pageNr) const;
bool searchTextOnPage(const std::string& text, size_t pageNumber, size_t index, size_t* occurrences,
XojPdfRectangle* matchRect);
bool cut();
bool copy();
bool paste();
void getPasteTarget(double& x, double& y) const;
bool actionDelete();
void endTextAllPages(XojPageView* except = nullptr) const;
void endSplineAllPages() const;
int getDisplayWidth() const;
int getDisplayHeight() const;
bool isPageVisible(size_t page, int* visibleHeight) const;
void ensureRectIsVisible(int x, int y, int width, int height);
void setSelection(EditSelection* selection);
EditSelection* getSelection() const;
void deleteSelection(EditSelection* sel = nullptr);
void repaintSelection(bool evenWithoutSelection = false);
TextEditor* getTextEditor() const;
std::vector<std::unique_ptr<XojPageView>> const& getViewPages() const;
Control* getControl() const;
double getZoom() const;
int getDpiScaleFactor() const;
Document* getDocument() const;
PdfCache* getCache() const;
RepaintHandler* getRepaintHandler() const;
GtkWidget* getWidget() const;
XournalppCursor* getCursor() const;
Layout* getLayout() const;
xoj::util::Rectangle<double>* getVisibleRect(size_t page) const;
xoj::util::Rectangle<double>* getVisibleRect(const XojPageView* redrawable) const;
/**
* Recreate the PDF cache, for example after the underlying PDF file has changed
*/
void recreatePdfCache();
/**
* A pen action was detected now, therefore ignore touch events
* for a short time
*/
void penActionDetected();
/**
* @return Helper class for Touch specific fixes
*/
HandRecognition* getHandRecognition() const;
/**
* @return Scrollbars
*/
ScrollHandling* getScrollHandling() const;
public:
// ZoomListener interface
void zoomChanged() override;
public:
// DocumentListener interface
void pageSelected(size_t page) override;
void pageSizeChanged(size_t page) override;
void pageChanged(size_t page) override;
void pageInserted(size_t page) override;
void pageDeleted(size_t page) override;
void documentChanged(DocumentChangeType type) override;
public:
bool onKeyPressEvent(const KeyEvent& event);
bool onKeyReleaseEvent(const KeyEvent& event);
void onSettingsChanged();
private:
void fireZoomChanged();
std::pair<size_t, size_t> preloadPageBounds(size_t page, size_t maxPage);
static auto clearMemoryTimer(XournalView* widget) -> gboolean;
void cleanupBufferCache();
private:
/**
* Scrollbars
*/
ScrollHandling* scrollHandling = nullptr;
GtkWidget* widget = nullptr;
std::vector<std::unique_ptr<XojPageView>> viewPages;
Control* control = nullptr;
size_t currentPage = 0;
size_t lastSelectedPage = npos;
std::unique_ptr<PdfCache> cache;
/**
* Handler for rerendering pages / repainting pages
*/
std::unique_ptr<RepaintHandler> repaintHandler;
/**
* Memory cleanup timeout
*/
guint cleanupTimeout = std::numeric_limits<guint>::max();
friend class Layout;
};
@@ -1,648 +0,0 @@
#include "XournalppCursor.h"
#include <cmath> // for cos, sin, M_PI, NAN, fmod
#include <cstdint> // for uint32_t
#include <initializer_list> // for initializer_list
#include <cairo.h> // for cairo_move_to, cairo_lin...
#include <gdk-pixbuf/gdk-pixbuf.h> // for GdkPixbuf
#include <glib-object.h> // for g_object_unref
#include <gtk/gtk.h> // for gtk_widget_get_window
#include "control/Control.h" // for Control
#include "control/ToolEnums.h" // for TOOL_HAND, TOOL_PEN, TOO...
#include "control/ToolHandler.h" // for ToolHandler
#include "control/settings/Settings.h" // for Settings
#include "control/settings/SettingsEnums.h" // for STYLUS_CURSOR_BIG, STYLU...
#include "control/zoom/ZoomControl.h" // for ZoomControl
#include "gui/MainWindow.h" // for MainWindow
#include "util/Color.h" // for argb_to_GdkRGBA, rgb_to_...
#include "util/safe_casts.h" // for ceil_cast
#include "XournalView.h" // for XournalView
// NOTE: Every cursor change must result in the setting of this->currentCursor to the new cursor type even for custom
// cursors.
// Custom cursors can also compare then set this->currentCursorFlavour to notice changes in colour or size etc. The
// flavour calculation is specific to each cursor type and is calculated differently within each type.
// All the cursors we want to use. WARNING Make sure to set their css names in cssCursors[] below WARNING
enum AVAILABLECURSORS {
CRSR_nullptr = 0, // <--- Do Not Modify
CRSR_BUSY,
CRSR_MOVE,
CRSR_MOVING,
CRSR_GRAB,
CRSR_GRABBING,
CRSR_TOP_LEFT_CORNER,
CRSR_TOP_RIGHT_CORNER,
CRSR_BOTTOM_LEFT_CORNER,
CRSR_BOTTOM_RIGHT_CORNER,
CRSR_SB_H_DOUBLE_ARROW,
CRSR_EXCHANGE,
CRSR_PIRATE,
CRSR_SB_V_DOUBLE_ARROW,
CRSR_ARROW,
CRSR_BLANK_CURSOR,
CRSR_XTERM,
CRSR_DEFAULT,
CRSR_HAND2,
CRSR_TCROSS,
CRSR_PENORHIGHLIGHTER,
CRSR_ERASER,
CRSR_DRAWDIRNONE, // drawdir* keep these consecutive and in order: none,shift,ctrl,shiftctrl
CRSR_DRAWDIRSHIFT, // "
CRSR_DRAWDIRCTRL, // "
CRSR_DRAWDIRSHIFTCTRL, // "
CRSR_RESIZE,
CRSR_END_OF_CURSORS
};
struct cursorStruct {
const gchar* cssName;
const gchar* cssBackupName;
};
// our enum mapped to css name and a place to store the cursor pointer.
// Note: including enum as error check (for now?)
cursorStruct cssCursors[CRSR_END_OF_CURSORS];
XournalppCursor::XournalppCursor(Control* control): control(control) {
// clang-format off
// NOTE: Go ahead and use a fancy css cursor... but specify a common backup cursor.
cssCursors[CRSR_nullptr ] = {"",""};
cssCursors[CRSR_BUSY ] = {"wait", "" };
cssCursors[CRSR_MOVE ] = {"all-scroll", "" };
cssCursors[CRSR_MOVING ] = {"grabbing", "" };
cssCursors[CRSR_GRAB ] = {"grab", "" };
cssCursors[CRSR_GRABBING ] = {"grabbing", "" };
cssCursors[CRSR_TOP_LEFT_CORNER ] = {"nw-resize", "" };
cssCursors[CRSR_TOP_RIGHT_CORNER ] = {"ne-resize", "" };
cssCursors[CRSR_BOTTOM_LEFT_CORNER ] = {"sw-resize", "" };
cssCursors[CRSR_BOTTOM_RIGHT_CORNER ] = {"se-resize", "" };
cssCursors[CRSR_SB_H_DOUBLE_ARROW ] = {"ew-resize", "" };
cssCursors[CRSR_EXCHANGE ] = {"exchange", "arrow" };
cssCursors[CRSR_PIRATE ] = {"pirate", "arrow" };
cssCursors[CRSR_SB_V_DOUBLE_ARROW ] = {"ns-resize", "" };
cssCursors[CRSR_ARROW ] = {"default", "" };
cssCursors[CRSR_BLANK_CURSOR ] = {"none", "" };
cssCursors[CRSR_XTERM ] = {"text", "" };
cssCursors[CRSR_DEFAULT ] = {"default", "" };
cssCursors[CRSR_HAND2 ] = {"hand2", "" };
cssCursors[CRSR_TCROSS ] = {"crosshair", "" };
cssCursors[CRSR_PENORHIGHLIGHTER ] = {"",""}; // custom cursors - enum used only for check
cssCursors[CRSR_ERASER ] = {"",""}; // "
cssCursors[CRSR_DRAWDIRNONE ] = {"",""}; // "
cssCursors[CRSR_DRAWDIRSHIFT ] = {"",""}; // "
cssCursors[CRSR_DRAWDIRCTRL ] = {"",""}; // "
cssCursors[CRSR_DRAWDIRSHIFTCTRL ] = {"",""}; // "
cssCursors[CRSR_RESIZE ] = {"",""}; // "
};
// clang-format on
constexpr auto RESIZE_CURSOR_SIZE = 16;
constexpr auto DELTA_ANGLE_ARROW_HEAD = M_PI / 6.0;
constexpr auto LENGTH_ARROW_HEAD = 0.7;
constexpr auto RESIZE_CURSOR_HASH_PRECISION = 1000;
XournalppCursor::~XournalppCursor() = default;
void XournalppCursor::setInputDeviceClass(InputDeviceClass device) { this->inputDevice = device; }
// pen or hi-light cursor will be a DrawDir cursor instead
void XournalppCursor::activateDrawDirCursor(bool enable, bool shift, bool ctrl) {
this->drawDirActive = enable;
this->drawDirShift = shift;
this->drawDirCtrl = ctrl;
}
void XournalppCursor::setMouseDown(bool mouseDown) {
if (this->mouseDown == mouseDown) {
return;
}
this->mouseDown = mouseDown;
ToolHandler* handler = control->getToolHandler();
ToolType type = handler->getToolType();
// Not always an update is needed
if (type == TOOL_HAND || type == TOOL_VERTICAL_SPACE || type == TOOL_ERASER) {
updateCursor();
}
}
void XournalppCursor::setMouseSelectionType(CursorSelectionType selectionType) {
if (this->selectionType == selectionType) {
return;
}
this->selectionType = selectionType;
updateCursor();
}
void XournalppCursor::setRotationAngle(double angle) { this->angle = angle; }
void XournalppCursor::setMirror(bool mirror) { this->mirror = mirror; }
/*This handles setting the busy cursor for the main window and calls
* updateCursor to set the busy cursor for the XournalWidget region.
*/
void XournalppCursor::setCursorBusy(bool busy) {
MainWindow* win = control->getWindow();
if (!win) {
return;
}
if (this->busy == busy) {
return;
}
this->busy = busy;
if (busy) {
GdkWindow* window = gtk_widget_get_window(win->getWindow());
xoj::util::GObjectSPtr<GdkCursor> cursor(
gdk_cursor_new_from_name(gdk_window_get_display(window), cssCursors[CRSR_BUSY].cssName),
xoj::util::adopt);
gdk_window_set_cursor(window, cursor.get());
} else {
if (gtk_widget_get_window(win->getWindow())) {
gdk_window_set_cursor(gtk_widget_get_window(win->getWindow()), nullptr);
}
}
updateCursor();
}
void XournalppCursor::setInsidePage(bool insidePage) {
if (this->insidePage == insidePage) {
return;
}
this->insidePage = insidePage;
updateCursor();
}
void XournalppCursor::setInvisible(bool invisible) {
if (this->invisible == invisible) {
return;
}
this->invisible = invisible;
updateCursor();
}
void XournalppCursor::updateCursor() {
MainWindow* win = control->getWindow();
if (!win) {
return;
}
XournalView* xournal = win->getXournal();
if (!xournal) {
return;
}
GdkCursor* cursor = nullptr;
if (this->busy) {
setCursor(CRSR_BUSY);
} else {
ToolHandler* handler = control->getToolHandler();
ToolType type = handler->getToolType();
if (type == TOOL_HAND) {
if (this->mouseDown) {
setCursor(CRSR_GRABBING);
} else {
setCursor(CRSR_GRAB);
}
} else if (!this->insidePage) {
setCursor(CRSR_DEFAULT);
} else if (this->selectionType) {
switch (this->selectionType) {
case CURSOR_SELECTION_MOVE:
if (this->mouseDown) {
setCursor(CRSR_MOVING);
} else {
setCursor(CRSR_MOVE);
}
break;
case CURSOR_SELECTION_TOP_LEFT:
[[fallthrough]];
case CURSOR_SELECTION_BOTTOM_RIGHT:
cursor = getResizeCursor(45);
break;
case CURSOR_SELECTION_TOP_RIGHT:
[[fallthrough]];
case CURSOR_SELECTION_BOTTOM_LEFT:
cursor = getResizeCursor(135);
break;
case CURSOR_SELECTION_LEFT:
[[fallthrough]];
case CURSOR_SELECTION_RIGHT:
cursor = getResizeCursor(180);
break;
case CURSOR_SELECTION_TOP:
[[fallthrough]];
case CURSOR_SELECTION_BOTTOM:
cursor = getResizeCursor(90);
break;
case CURSOR_SELECTION_ROTATE:
setCursor(CRSR_EXCHANGE);
break;
case CURSOR_SELECTION_DELETE:
setCursor(CRSR_PIRATE);
break;
default:
break;
}
} else if (type == TOOL_PEN || type == TOOL_HIGHLIGHTER || type == TOOL_LASER_POINTER_PEN ||
type == TOOL_LASER_POINTER_HIGHLIGHTER) {
if (this->inputDevice == INPUT_DEVICE_MOUSE && !this->mouseDown) {
// mouse and not pressed
setCursor(CRSR_ARROW);
} else {
if (type == TOOL_PEN || type == TOOL_LASER_POINTER_PEN) {
cursor = getPenCursor();
} else // must be: if (type == TOOL_HIGHLIGHTER || type == TOOL_LASER_POINTER_HIGHLIGHTER)
{
cursor = getHighlighterCursor();
}
}
} else if (type == TOOL_ERASER) {
EraserVisibility visibility = control->getSettings()->getEraserVisibility();
if ((this->inputDevice == INPUT_DEVICE_PEN || this->inputDevice == INPUT_DEVICE_ERASER) &&
(visibility == ERASER_VISIBILITY_NEVER || (visibility == ERASER_VISIBILITY_HOVER && this->mouseDown) ||
(visibility == ERASER_VISIBILITY_TOUCH && !this->mouseDown))) {
setCursor(CRSR_BLANK_CURSOR);
} else {
cursor = getEraserCursor();
}
}
else if (type == TOOL_TEXT) {
if (this->invisible) {
setCursor(CRSR_BLANK_CURSOR);
} else {
setCursor(CRSR_XTERM);
}
} else if (type == TOOL_IMAGE) {
setCursor(CRSR_TCROSS);
} else if (type == TOOL_FLOATING_TOOLBOX) {
setCursor(CRSR_DEFAULT);
} else if (type == TOOL_VERTICAL_SPACE) {
setCursor(CRSR_SB_V_DOUBLE_ARROW);
} else if (type == TOOL_SELECT_OBJECT) {
setCursor(CRSR_DEFAULT);
} else if (type == TOOL_PLAY_OBJECT) {
setCursor(CRSR_HAND2);
} else if (type == TOOL_SELECT_PDF_TEXT_LINEAR) {
setCursor(CRSR_XTERM);
} else // other selections are handled before anyway, because you can move a selection with every tool
{
setCursor(CRSR_TCROSS);
}
}
GdkWindow* window = gtk_widget_get_window(xournal->getWidget());
if (window) {
if (cursor != nullptr) {
gdk_window_set_cursor(window, cursor);
}
gtk_widget_set_sensitive(xournal->getWidget(), !this->busy);
}
// Performance removal!
// If this line is ever required, make sure this function is never called, when the cursor is not set.
// gdk_display_sync(gdk_display_get_default());
if (cursor != nullptr) {
g_object_unref(cursor);
}
}
auto XournalppCursor::getResizeCursor(double deltaAngle) -> GdkCursor* {
if (this->mirror) {
deltaAngle = -deltaAngle;
}
gulong flavour = static_cast<gulong>(RESIZE_CURSOR_HASH_PRECISION * fmod(angle + deltaAngle, 180.0));
if (CRSR_RESIZE == this->currentCursor && flavour == this->currentCursorFlavour) {
return nullptr;
}
this->currentCursor = CRSR_RESIZE;
this->currentCursorFlavour = flavour;
double a = (this->angle + deltaAngle) * M_PI / 180;
cairo_surface_t* crCursor = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, RESIZE_CURSOR_SIZE, RESIZE_CURSOR_SIZE);
cairo_t* cr = cairo_create(crCursor);
cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1);
cairo_translate(cr, RESIZE_CURSOR_SIZE / 2, RESIZE_CURSOR_SIZE / 2);
cairo_scale(cr, RESIZE_CURSOR_SIZE / 2, RESIZE_CURSOR_SIZE / 2);
cairo_set_line_width(cr, 0.2);
// draw double headed arrow rotated accordingly
cairo_move_to(cr, cos(a), sin(a));
cairo_line_to(cr, -cos(a), -sin(a));
cairo_stroke(cr);
// head and tail
for (auto s: {-1, 1}) {
cairo_move_to(cr, s * cos(a), s * sin(a));
cairo_rel_line_to(cr, s * cos(a + M_PI + DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD,
s * sin(a + M_PI + DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD);
cairo_move_to(cr, s * cos(a), s * sin(a));
cairo_rel_line_to(cr, s * cos(a + M_PI - DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD,
s * sin(a + M_PI - DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD);
cairo_stroke(cr);
}
cairo_destroy(cr);
GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(crCursor, 0, 0, RESIZE_CURSOR_SIZE, RESIZE_CURSOR_SIZE);
cairo_surface_destroy(crCursor);
GdkCursor* cursor =
gdk_cursor_new_from_pixbuf(gtk_widget_get_display(control->getWindow()->getXournal()->getWidget()), pixbuf,
RESIZE_CURSOR_SIZE / 2, RESIZE_CURSOR_SIZE / 2);
g_object_unref(pixbuf);
return cursor;
}
auto XournalppCursor::getEraserCursor() -> GdkCursor* {
// Eraser's size follow a quadratic increment, so the cursor will do the same
double cursorSize = control->getToolHandler()->getThickness() * 2.0 * control->getZoomControl()->getZoom();
gulong flavour = static_cast<gulong>(64 * cursorSize);
if (CRSR_ERASER == this->currentCursor && flavour == this->currentCursorFlavour) {
return nullptr; // cursor already set
}
this->currentCursor = CRSR_ERASER;
this->currentCursorFlavour = flavour;
cairo_surface_t* surface =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ceil_cast<int>(cursorSize), ceil_cast<int>(cursorSize));
cairo_t* cr = cairo_create(surface);
cairo_rectangle(cr, 0, 0, cursorSize, cursorSize);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);
cairo_rectangle(cr, 0, 0, cursorSize, cursorSize);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_stroke(cr);
cairo_destroy(cr);
GdkCursor* cursor =
gdk_cursor_new_from_surface(gdk_display_get_default(), surface, cursorSize / 2.0, cursorSize / 2.0);
cairo_surface_destroy(surface);
return cursor;
}
auto XournalppCursor::getHighlighterCursor() -> GdkCursor* {
if (this->drawDirActive) {
return createCustomDrawDirCursor(48, this->drawDirShift, this->drawDirCtrl);
}
return createHighlighterOrPenCursor(120 / 255.0);
}
auto XournalppCursor::getPenCursor() -> GdkCursor* {
if ((control->getSettings()->getStylusCursorType() == STYLUS_CURSOR_NONE) &&
!control->getSettings()->isHighlightPosition()) {
setCursor(CRSR_BLANK_CURSOR);
return nullptr;
}
if (control->getSettings()->getStylusCursorType() == STYLUS_CURSOR_ARROW) {
setCursor(CRSR_ARROW);
return nullptr;
}
if ((control->getSettings()->getStylusCursorType() != STYLUS_CURSOR_NONE) && this->drawDirActive) {
return createCustomDrawDirCursor(48, this->drawDirShift, this->drawDirCtrl);
}
return createHighlighterOrPenCursor(1.0);
}
auto XournalppCursor::createHighlighterOrPenCursor(double alpha) -> GdkCursor* {
auto irgb = control->getToolHandler()->getColor();
if (const auto& params = control->getSettings()->getRecolorParameters(); params.recolorizeMainView) {
irgb = params.recolor.convertColor(irgb);
}
auto drgb = Util::rgb_to_GdkRGBA(irgb);
auto cursorType = control->getSettings()->getStylusCursorType();
auto cursor = (cursorType == STYLUS_CURSOR_NONE) ? CRSR_BLANK_CURSOR : CRSR_PENORHIGHLIGHTER;
bool bright = control->getSettings()->isHighlightPosition();
double cursorSize = std::min(90., control->getToolHandler()->getThickness() * control->getZoomControl()->getZoom());
int height = ceil_cast<int>(cursorSize);
int width = height;
// create a hash of variables so we notice if one changes despite being the same cursor type:
gulong flavour = (cursorType == STYLUS_CURSOR_DOT ? 1U : 0U) | (cursorType == STYLUS_CURSOR_BIG ? 2U : 0U) |
(bright ? 4U : 0U) | static_cast<gulong>(64 * alpha) << 3U |
static_cast<gulong>(cursorSize) << 10U | static_cast<gulong>(uint32_t(irgb)) << 15U;
if ((cursor == this->currentCursor) && (flavour == this->currentCursorFlavour)) {
return nullptr;
}
this->currentCursor = cursor;
this->currentCursorFlavour = flavour;
if ((cursorType == STYLUS_CURSOR_BIG) || bright) {
height = width = 90;
}
// We change the drawing method, now the center with the colored dot of the pen
// is at the center of the cairo surface, and when we load the cursor, we load it
// with the relative offset
int centerX = width / 2;
int centerY = height / 2;
cairo_surface_t* crCursor = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cairo_t* cr = cairo_create(crCursor);
if (cursorType == STYLUS_CURSOR_BIG) {
// When using highlighter, paint the icon with the current color
if (alpha != 1.0) {
gdk_cairo_set_source_rgba(cr, &drgb);
} else {
cairo_set_source_rgb(cr, 1, 1, 1);
}
cairo_set_line_width(cr, 1.2);
// Starting point
cairo_move_to(cr, centerX + 2, centerY);
// Pencil cursor
cairo_line_to(cr, centerX + 2, centerY - 4);
cairo_line_to(cr, centerX + 15, centerY - 17.5);
cairo_line_to(cr, centerX + 19, centerY - 14);
cairo_line_to(cr, centerX + 6, centerY);
cairo_close_path(cr);
cairo_fill_preserve(cr);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_stroke(cr);
cairo_fill_preserve(cr);
}
if (bright) {
// Highlight cursor with a circle
auto&& color = Util::argb_to_GdkRGBA(control->getSettings()->getCursorHighlightColor());
gdk_cairo_set_source_rgba(cr, &color);
cairo_arc(cr, centerX, centerY, control->getSettings()->getCursorHighlightRadius(), 0, 2 * M_PI);
cairo_fill_preserve(cr);
auto&& borderColor = Util::argb_to_GdkRGBA(control->getSettings()->getCursorHighlightBorderColor());
gdk_cairo_set_source_rgba(cr, &borderColor);
cairo_set_line_width(cr, control->getSettings()->getCursorHighlightBorderWidth());
cairo_stroke(cr);
}
if (cursorType != STYLUS_CURSOR_NONE) {
auto drgbCopy = drgb;
drgbCopy.alpha = alpha;
gdk_cairo_set_source_rgba(cr, &drgbCopy);
cairo_arc(cr, centerX, centerY, cursorSize / 2., 0, 2. * M_PI);
cairo_fill(cr);
}
cairo_destroy(cr);
GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(crCursor, 0, 0, width, height);
cairo_surface_destroy(crCursor);
GdkCursor* gdkCursor = gdk_cursor_new_from_pixbuf(
gtk_widget_get_display(control->getWindow()->getXournal()->getWidget()), pixbuf, centerX, centerY);
g_object_unref(pixbuf);
return gdkCursor;
}
void XournalppCursor::setCursor(guint cursorID) {
if (cursorID == this->currentCursor) {
return;
}
MainWindow* win = control->getWindow();
if (!win) {
return;
}
XournalView* xournal = win->getXournal();
if (!xournal) {
return;
}
GdkWindow* window = gtk_widget_get_window(xournal->getWidget());
if (!window) {
return;
}
GdkCursor* cursor = gdk_cursor_new_from_name(gdk_window_get_display(window), cssCursors[cursorID].cssName);
if (cursor == nullptr) // failed to get a cursor, try backup cursor.
{
if (cursorID != CRSR_nullptr) {
cursor = gdk_cursor_new_from_name(gdk_window_get_display(window), cssCursors[cursorID].cssBackupName);
// Null cursor is ok but not wanted ... warn user
if (cursor == nullptr) {
if (CRSR_nullptr == this->currentCursor) {
return; // We've already been here
}
g_warning("CSS Cursor and backup not valid '%s', '%s'", cssCursors[cursorID].cssName,
cssCursors[cursorID].cssBackupName);
}
}
cursorID = cursor == nullptr ? CRSR_nullptr : cursorID;
}
this->currentCursor = cursorID;
gdk_window_set_cursor(gtk_widget_get_window(xournal->getWidget()), cursor);
gdk_window_set_cursor(window, cursor);
if (cursor) {
g_object_unref(cursor);
}
}
auto XournalppCursor::createCustomDrawDirCursor(int size, bool shift, bool ctrl) -> GdkCursor* {
bool big = control->getSettings()->getStylusCursorType() == STYLUS_CURSOR_BIG;
bool bright = control->getSettings()->isHighlightPosition();
guint newCursorID = CRSR_DRAWDIRNONE + (shift ? 1 : 0) + (ctrl ? 2 : 0);
gulong flavour =
(big ? 1 : 0) | (bright ? 2 : 0) | static_cast<gulong>(size) << 2; // hash of variables for comparison only
if (newCursorID == this->currentCursor && flavour == this->currentCursorFlavour) {
return nullptr;
}
this->currentCursor = newCursorID;
this->currentCursorFlavour = flavour;
int height = size;
int width = size;
int fontSize = 8;
if (big || bright) {
height = width = 60;
fontSize = 12;
}
int centerX = width - width / 4;
int centerY = height - height / 4;
cairo_surface_t* crCursor = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cairo_t* cr = cairo_create(crCursor);
cairo_set_line_width(cr, 1.2);
// Starting point
cairo_move_to(cr, centerX, height / 2);
cairo_line_to(cr, centerX, height);
cairo_stroke(cr);
cairo_move_to(cr, width / 2, centerY);
cairo_line_to(cr, width, centerY);
cairo_stroke(cr);
if (ctrl) {
cairo_text_extents_t extents;
const char* utf8 = "CONTROL";
double x = NAN, y = NAN;
cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, fontSize);
cairo_text_extents(cr, utf8, &extents);
x = 0;
y = extents.height;
cairo_move_to(cr, x, y);
cairo_show_text(cr, utf8);
}
if (shift) {
cairo_text_extents_t extents;
const char* utf8 = "SHIFT";
double x = NAN, y = NAN;
cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, fontSize);
cairo_text_extents(cr, utf8, &extents);
x = 0;
y = extents.height * 2.5;
cairo_move_to(cr, x, y);
cairo_show_text(cr, utf8);
}
cairo_destroy(cr);
GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(crCursor, 0, 0, width, height);
cairo_surface_destroy(crCursor);
GdkCursor* cursor = gdk_cursor_new_from_pixbuf(
gtk_widget_get_display(control->getWindow()->getXournal()->getWidget()), pixbuf, centerX, centerY);
g_object_unref(pixbuf);
return cursor;
}
@@ -1,76 +0,0 @@
/*
* Xournal++
*
* Handles the XournalppCursor
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <gdk/gdk.h> // for GdkCursor
#include <glib.h> // for guint, gulong
#include "control/tools/CursorSelectionType.h" // for CursorSelectionType
#include "gui/inputdevices/InputEvents.h" // for InputDeviceClass, INP...
class Control;
class XournalppCursor {
public:
XournalppCursor(Control* control);
virtual ~XournalppCursor();
void setCursorBusy(bool busy);
void updateCursor();
void setMouseSelectionType(CursorSelectionType selectionType);
void setMouseDown(bool mouseDown);
void setInvisible(bool invisible);
void setInsidePage(bool insidePage);
void activateDrawDirCursor(bool enable, bool shift = false, bool ctrl = false);
void setInputDeviceClass(InputDeviceClass inputDevice);
void setRotationAngle(double angle);
void setMirror(bool mirror);
private:
void setCursor(guint id);
GdkCursor* getPenCursor();
GdkCursor* getEraserCursor();
GdkCursor* getHighlighterCursor();
GdkCursor* getResizeCursor(double deltaAngle);
GdkCursor* createHighlighterOrPenCursor(double alpha);
GdkCursor* createCustomDrawDirCursor(int size, bool shift, bool ctrl);
private:
InputDeviceClass inputDevice = INPUT_DEVICE_MOUSE;
Control* control = nullptr;
bool busy = false;
bool insidePage = false;
CursorSelectionType selectionType = CURSOR_SELECTION_NONE;
bool mouseDown = false;
bool invisible = false;
// One shot drawDir custom cursor -drawn instead of pen/stylus then cleared.
bool drawDirActive = false;
bool drawDirShift = false;
bool drawDirCtrl = false;
// avoid re-assigning same cursor
guint currentCursor = 0; // enum AVAILABLECURSORS
gulong currentCursorFlavour{}; // for different flavours of a cursor (i.e. drawdir, pen and highlighter custom
// cursors)
// for resizing rotated/mirrored selections
double angle = 0;
bool mirror = false;
};
@@ -1,76 +0,0 @@
#include "AboutDialog.h"
#include <gtk/gtk.h>
#include "gui/Builder.h"
#include "util/PlaceholderString.h" // for PlaceholderString
#include "util/gtk4_helper.h" // for gtk_box_append
#include "util/i18n.h" // for _
#include "config-git.h" // for GIT_COMMIT_ID
#include "config.h" // for PROJECT_VERSION
class GladeSearchpath;
constexpr auto UI_FILE = "about.glade";
constexpr auto UI_DIALOG_NAME = "aboutDialog";
constexpr auto GIT_REPO = "https://github.com/xournalpp/xournalpp";
constexpr auto WEBSITE = "https://xournalpp.github.io";
constexpr auto AUTHORS_LINK = "https://raw.githubusercontent.com/xournalpp/xournalpp/master/AUTHORS";
constexpr auto LICENCE_LINK = "https://raw.githubusercontent.com/xournalpp/xournalpp/master/LICENSE";
static GtkWindow* constructWindow(GladeSearchpath* gladeSearchPath) {
Builder builder(gladeSearchPath, UI_FILE);
GtkWindow* window = GTK_WINDOW(builder.get(UI_DIALOG_NAME));
auto insertPropertyKey = [](GtkGrid* grid, std::string&& str, gint top) {
auto widget = gtk_label_new(nullptr);
gtk_label_set_markup(GTK_LABEL(widget), str.insert(0, "<b>").append("</b>").c_str());
gtk_widget_set_halign(widget, GtkAlign::GTK_ALIGN_START);
gtk_grid_attach(grid, widget, 0, top, 1, 1);
};
auto insertPropertyValue = [](GtkGrid* grid, std::string const& str, gint top) {
auto widget = gtk_label_new(str.c_str());
gtk_widget_set_halign(widget, GtkAlign::GTK_ALIGN_START);
gtk_grid_attach(grid, widget, 1, top, 1, 1);
};
auto infoGrid = GTK_GRID(builder.get("versionInfoGrid"));
insertPropertyKey(infoGrid, _("Version"), 0);
insertPropertyValue(infoGrid, PROJECT_VERSION, 0);
insertPropertyKey(infoGrid, _("Built on"), 1);
insertPropertyValue(infoGrid, __DATE__ ", " __TIME__, 1);
insertPropertyKey(infoGrid, _("GTK Version"), 2);
auto gtkVersion =
FS(FORMAT_STR("{1}.{2}.{3}") % gtk_get_major_version() % gtk_get_minor_version() % gtk_get_micro_version());
insertPropertyValue(infoGrid, gtkVersion, 2);
auto const gitCommitId = std::string{GIT_COMMIT_ID};
if (!gitCommitId.empty()) {
insertPropertyKey(infoGrid, _("Git commit"), 3);
insertPropertyValue(infoGrid, gitCommitId.c_str(), 3);
}
gtk_box_append(GTK_BOX(builder.get("vboxRepo")), gtk_link_button_new(GIT_REPO));
gtk_box_append(GTK_BOX(builder.get("vboxWebsite")), gtk_link_button_new(WEBSITE));
gtk_box_append(GTK_BOX(builder.get("vboxCommunity")),
gtk_link_button_new_with_label(AUTHORS_LINK, _("See the full list of contributors")));
gtk_box_append(GTK_BOX(builder.get("vboxLicense")),
gtk_link_button_new_with_label(LICENCE_LINK, _("GNU GPLv2 or later")));
g_signal_connect_swapped(GTK_BUTTON(builder.get("closebutton")), "clicked", G_CALLBACK(gtk_window_close), window);
#if GTK_MAJOR_VERSION == 3
// Widgets are visible by default in gtk4
gtk_widget_show_all(builder.get("dialog-main-box"));
#endif
return window;
}
xoj::popup::AboutDialog::AboutDialog(GladeSearchpath* gladeSearchPath): window(constructWindow(gladeSearchPath)) {}
xoj::popup::AboutDialog::~AboutDialog() = default;
@@ -1,29 +0,0 @@
/*
* Xournal++
*
* The about dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include "util/raii/GtkWindowUPtr.h"
class GladeSearchpath;
namespace xoj::popup {
class AboutDialog {
public:
AboutDialog(GladeSearchpath* gladeSearchPath);
~AboutDialog();
inline GtkWindow* getWindow() const { return window.get(); }
private:
xoj::util::GtkWindowUPtr window;
};
}; // namespace xoj::popup
@@ -1,106 +0,0 @@
#include "gui/dialog/AbstractLatexDialog.h"
#include <algorithm> // for max, min
#include <memory>
#include "control/LatexController.h"
#include "model/TexImage.h"
#include "util/Range.h"
// Default background color of the preview.
const Color DEFAULT_PREVIEW_BACKGROUND = Colors::white;
AbstractLatexDialog::AbstractLatexDialog(std::unique_ptr<LatexController> ctrl):
texCtrl{std::move(ctrl)}, previewBackgroundColor{DEFAULT_PREVIEW_BACKGROUND} {
texCtrl->dlg = this;
}
AbstractLatexDialog::~AbstractLatexDialog() { texCtrl->dlg = nullptr; };
void AbstractLatexDialog::setCompilationStatus(bool isTexValid, bool isCompilationDone,
const std::string& compilationOutput) {
gtk_widget_set_sensitive(GTK_WIDGET(btOk), isTexValid && isCompilationDone);
// Show error warning only if LaTeX is invalid.
gtk_widget_set_opacity(GTK_WIDGET(texErrorLabel), isTexValid ? 0 : 1);
// Update the output pane.
gtk_text_buffer_set_text(compilationOutputTextBuffer, compilationOutput.c_str(), -1);
}
void AbstractLatexDialog::setTempRender(PopplerDocument* pdf) {
if (poppler_document_get_n_pages(pdf) < 1) {
return;
}
this->previewPdfPage.reset(poppler_document_get_page(pdf, 0), xoj::util::adopt);
this->previewMask.reset();
// Queue rendering the changed preview.
gtk_widget_queue_draw(GTK_WIDGET(this->previewDrawingArea));
}
void AbstractLatexDialog::setPreviewBackgroundColor(Color newColor) { this->previewBackgroundColor = newColor; }
void AbstractLatexDialog::previewDrawFunc(GtkDrawingArea*, cairo_t* cr, int width, int height,
AbstractLatexDialog* self) {
// If we have nothing to render, do nothing!
if (!self->previewPdfPage) {
return;
}
Range pageRange(0, 0, 0, 0);
poppler_page_get_size(self->previewPdfPage.get(), &pageRange.maxX, &pageRange.maxY);
const double zoom =
std::min(static_cast<double>(width) / pageRange.maxX, static_cast<double>(height) / pageRange.maxY);
if (!self->previewMask.isInitialized() || self->previewMask.getZoom() < zoom) {
// Need to rerender the preview
self->previewMask = xoj::view::Mask(gtk_widget_get_scale_factor(GTK_WIDGET(self->previewDrawingArea)),
pageRange, zoom, CAIRO_CONTENT_COLOR_ALPHA);
poppler_page_render(self->previewPdfPage.get(), self->previewMask.get());
}
Util::cairo_set_source_rgbi(cr, self->previewBackgroundColor);
cairo_paint(cr);
// Center the rendering
const double extraHorizontalSpace = std::max(0.0, width - pageRange.maxX * zoom);
const double extraVerticalSpace = std::max(0.0, height - pageRange.maxY * zoom);
cairo_translate(cr, 0.5 * extraHorizontalSpace, 0.5 * extraVerticalSpace);
cairo_scale(cr, zoom, zoom);
self->previewMask.paintTo(cr);
}
void AbstractLatexDialog::populateStandardWidgetsFromBuilder(Builder& builder) {
previewDrawingArea = GTK_DRAWING_AREA(builder.get("texImage"));
btOk = GTK_BUTTON(builder.get("btOk"));
btCancel = GTK_BUTTON(builder.get("btCancel"));
texErrorLabel = GTK_LABEL(builder.get("texErrorLabel"));
compilationOutputTextBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(builder.get("texCommandOutputText")));
}
void AbstractLatexDialog::connectStandardSignals() {
g_signal_connect(btCancel, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) {
auto* self = static_cast<AbstractLatexDialog*>(d);
self->texCtrl->cancelEditing();
gtk_window_close(self->window.get());
}),
this);
g_signal_connect(btOk, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) {
auto* self = static_cast<AbstractLatexDialog*>(d);
self->texCtrl->insertTexImage();
gtk_window_close(self->window.get());
}),
this);
gtk_drawing_area_set_draw_func(this->previewDrawingArea, GtkDrawingAreaDrawFunc(previewDrawFunc), this, nullptr);
if (texCtrl->temporaryRender) {
// Use preexisting pdf
setTempRender(texCtrl->temporaryRender->getPdf());
}
}
@@ -1,93 +0,0 @@
/*
* Xournal++
*
* Latex implementation
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <memory> // for unique_ptr
#include <poppler.h> // for PopplerDocument
#include "gui/Builder.h" // for Builder
#include "util/Color.h" // for Color
#include "util/raii/GObjectSPtr.h" // for GObjectSPtr
#include "util/raii/GtkWindowUPtr.h" // for GtkWindowUPtr
#include "view/Mask.h" // for Mask
class LatexController;
class AbstractLatexDialog {
public:
AbstractLatexDialog() = delete;
AbstractLatexDialog(const AbstractLatexDialog& other) = delete;
AbstractLatexDialog& operator=(const AbstractLatexDialog& other) = delete;
AbstractLatexDialog(std::unique_ptr<LatexController> texCtrl);
virtual ~AbstractLatexDialog();
public:
virtual void setCompilationStatus(bool isTexValid, bool isCompilationDone, const std::string& compilationOutput);
virtual std::string getBufferContents() = 0;
/**
* Set temporary Tex render and queue a re-draw.
* @param pdf PDF document with rendered TeX.
*/
void setTempRender(PopplerDocument* pdf);
/**
* Set TeX preview background color to the given color.
* @param color New preview background color.
*/
void setPreviewBackgroundColor(Color color);
inline GtkWindow* getWindow() const { return window.get(); }
protected:
/**
* @brief Preview DrawingArea draw function
*/
static void previewDrawFunc(GtkDrawingArea* drawing_area, cairo_t* cr, int width, int height,
AbstractLatexDialog* self);
/**
* @brief populate the widgets stored in AbstractLatexDialog from the given builder with their
* standard names.
*/
void populateStandardWidgetsFromBuilder(Builder& builder);
/**
* Connect the cancel button to cancelEditing, the Ok button to insertTexImage as well as the
* draw handler for the preview area.
*/
void connectStandardSignals();
protected:
std::unique_ptr<LatexController> texCtrl;
xoj::util::GtkWindowUPtr window;
GtkButton* btOk;
GtkButton* btCancel;
// Preview
GtkDrawingArea* previewDrawingArea;
xoj::view::Mask previewMask;
/**
* Source page from which we render the preview.
*/
xoj::util::GObjectSPtr<PopplerPage> previewPdfPage;
GtkLabel* texErrorLabel;
GtkTextBuffer* compilationOutputTextBuffer;
/**
* Background color for the LaTeX preview.
*/
Color previewBackgroundColor;
};
@@ -1,284 +0,0 @@
#include "ButtonConfigGui.h"
#include <memory> // for allocator, allocator_trai...
#include <utility> // for pair
#include <gdk-pixbuf/gdk-pixbuf.h> // for GDK_TYPE_PIXBUF, GdkPixbuf
#include <gdk/gdk.h> // for GdkRGBA
#include <glib-object.h> // for g_value_get_int, GValue
#include "control/DeviceListHelper.h" // for InputDevice, getDeviceList
#include "control/settings/ButtonConfig.h" // for ButtonConfig
#include "control/settings/Settings.h" // for Settings
#include "gui/Builder.h"
#include "util/Color.h" // for GdkRGBA_to_argb, rgb_to_G...
#include "util/gtk4_helper.h"
#include "util/i18n.h" // for _
class GladeSearchpath;
constexpr auto UI_FILE = "settingsButtonConfig.glade";
constexpr auto UI_WIDGET_NAME = "mainBox";
ButtonConfigGui::ToolSizeIndexMap ButtonConfigGui::toolSizeIndexMap = {{0, TOOL_SIZE_NONE}, {1, TOOL_SIZE_VERY_FINE},
{2, TOOL_SIZE_FINE}, {3, TOOL_SIZE_MEDIUM},
{4, TOOL_SIZE_THICK}, {5, TOOL_SIZE_VERY_THICK}};
std::string ButtonConfigGui::toolSizeToLabel(ToolSize size) {
switch (size) {
case TOOL_SIZE_NONE:
return _("Thickness - don't change");
case TOOL_SIZE_VERY_FINE:
return _("Very thin");
case TOOL_SIZE_FINE:
return _("Thin");
case TOOL_SIZE_MEDIUM:
return _("Medium");
case TOOL_SIZE_THICK:
return _("Thick");
case TOOL_SIZE_VERY_THICK:
return _("Very thick");
default:
return "";
}
}
ButtonConfigGui::ButtonConfigGui(GladeSearchpath* gladeSearchPath, GtkBox* box, Settings* settings, unsigned int button,
bool withDevice):
settings(settings), button(button), withDevice(withDevice), iconNameHelper(settings) {
Builder builder(gladeSearchPath, UI_FILE);
gtk_box_append(box, builder.get(UI_WIDGET_NAME)); // box takes ownership of it all!
this->cbDevice = builder.get("labelDevice");
this->cbDisableDrawing = builder.get("cbDisableDrawing");
if (withDevice) {
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDevice), _("No device"));
this->deviceList = DeviceListHelper::getDeviceList(this->settings, true);
for (InputDevice const& dev: this->deviceList) {
std::string txt = dev.getName() + " (" + dev.getType() + ")";
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDevice), txt.c_str());
}
} else {
gtk_widget_hide(builder.get("lbDevice"));
gtk_widget_hide(this->cbDevice);
gtk_widget_hide(this->cbDisableDrawing);
}
GtkListStore* typeModel = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
auto addTypeCB = [&](const char* icon, const char* name, ToolType action) {
GtkTreeIter iter;
gtk_list_store_append(typeModel, &iter);
gtk_list_store_set(typeModel, &iter, 0, iconNameHelper.iconName(icon).c_str(), 1, name, 2, action, -1);
};
addTypeCB("transparent", _("Tool - don't change"), TOOL_NONE);
addTypeCB("tool-pencil", _("Pen"), TOOL_PEN);
addTypeCB("tool-eraser", _("Eraser"), TOOL_ERASER);
addTypeCB("tool-highlighter", _("Highlighter"), TOOL_HIGHLIGHTER);
addTypeCB("tool-text", _("Text"), TOOL_TEXT);
addTypeCB("tool-image", _("Insert image"), TOOL_IMAGE);
addTypeCB("laser-pointer", _("Laser Pointer - Pen"), TOOL_LASER_POINTER_PEN);
addTypeCB("laser-pointer", _("Laser Pointer - Highlighter"), TOOL_LASER_POINTER_HIGHLIGHTER);
addTypeCB("spacer", _("Vertical space"), TOOL_VERTICAL_SPACE);
addTypeCB("select-lasso", _("Select region"), TOOL_SELECT_REGION);
addTypeCB("select-rect", _("Select rectangle"), TOOL_SELECT_RECT);
addTypeCB("select-multilayer-lasso", _("Select multi layer region"), TOOL_SELECT_MULTILAYER_REGION);
addTypeCB("select-multilayer-rect", _("Select multi layer rect"), TOOL_SELECT_MULTILAYER_RECT);
addTypeCB("hand", _("Hand"), TOOL_HAND);
addTypeCB("floating-toolbox", _("Floating Toolbox (experimental)"), TOOL_FLOATING_TOOLBOX);
addTypeCB("select-pdf-text-ht", _("Select Text from pdf"), TOOL_SELECT_PDF_TEXT_LINEAR);
addTypeCB("select-pdf-text-area", _("Select Area Text from pdf"), TOOL_SELECT_PDF_TEXT_RECT);
this->cbTool = builder.get("cbTool");
gtk_combo_box_set_model(GTK_COMBO_BOX(this->cbTool), GTK_TREE_MODEL(typeModel));
g_signal_connect(cbTool, "changed", G_CALLBACK(&cbSelectCallback), this);
GtkCellRenderer* renderer = gtk_cell_renderer_pixbuf_new();
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(this->cbTool), renderer, false);
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(this->cbTool), renderer, "icon-name", 0, nullptr);
renderer = gtk_cell_renderer_text_new();
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(this->cbTool), renderer, true);
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(this->cbTool), renderer, "text", 1, nullptr);
this->cbThickness = builder.get("cbThickness");
for (auto const& size: toolSizeIndexMap) {
gtk_combo_box_text_insert_text(GTK_COMBO_BOX_TEXT(cbThickness), size.first,
toolSizeToLabel(size.second).c_str());
}
gtk_combo_box_set_active(GTK_COMBO_BOX(cbThickness), 0);
this->colorButton = builder.get("colorButton");
this->cbDrawingType = builder.get("cbDrawingType");
// DRAWING_TYPE_DONT_CHANGE
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Drawing Type - don't change"));
// DRAWING_TYPE_DEFAULT
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Normal drawing"));
// DRAWING_TYPE_RULER
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Line"));
// DRAWING_TYPE_RECTANGLE
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Rectangle"));
// DRAWING_TYPE_ELLIPSE
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Ellipse"));
// DRAWING_TYPE_ARROW
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Arrow"));
// DRAWING_TYPE_DOUBLE_ARROW
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Double Arrow"));
// DRAWING_TYPE_COORDINATE_SYSTEM
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Coordinate System"));
// DRAWING_TYPE_STROKE_RECOGNIZER
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Shape Recognizer"));
// DRAWING_TYPE_SPLINE
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Spline"));
// Values in glade GUI!
this->cbEraserType = builder.get("cbEraserType");
// Possible values are defined in the .glade file
this->cbStrokeType = builder.get("cbStrokeType");
loadSettings();
}
void ButtonConfigGui::loadSettings() {
ButtonConfig* cfg = settings->getButtonConfig(button);
GtkTreeModel* model = gtk_combo_box_get_model(GTK_COMBO_BOX(cbTool));
GtkTreeIter iter;
if (!gtk_tree_model_get_iter_first(model, &iter)) {
return;
}
GValue value = {0};
int i = 0;
gtk_combo_box_set_active(GTK_COMBO_BOX(cbTool), 0);
do {
gtk_tree_model_get_value(model, &iter, 2, &value);
int action = g_value_get_int(&value);
if (action == cfg->action) {
gtk_combo_box_set_active(GTK_COMBO_BOX(cbTool), i);
break;
}
i++;
g_value_unset(&value);
} while (gtk_tree_model_iter_next(model, &iter));
// select thickness in combo box
gtk_combo_box_set_active(GTK_COMBO_BOX(cbThickness), 0);
for (auto const& size: toolSizeIndexMap) {
if (size.second == cfg->size) {
gtk_combo_box_set_active(GTK_COMBO_BOX(cbThickness), size.first);
}
}
GdkRGBA color = Util::rgb_to_GdkRGBA(cfg->color);
gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(colorButton), &color);
gtk_combo_box_set_active(GTK_COMBO_BOX(this->cbDrawingType), cfg->drawingType);
if (cfg->eraserMode == ERASER_TYPE_DEFAULT) {
gtk_combo_box_set_active(GTK_COMBO_BOX(cbEraserType), 1);
} else if (cfg->eraserMode == ERASER_TYPE_WHITEOUT) {
gtk_combo_box_set_active(GTK_COMBO_BOX(cbEraserType), 2);
} else if (cfg->eraserMode == ERASER_TYPE_DELETE_STROKE) {
gtk_combo_box_set_active(GTK_COMBO_BOX(cbEraserType), 3);
} else {
gtk_combo_box_set_active(GTK_COMBO_BOX(cbEraserType), 0);
}
gtk_combo_box_set_active(GTK_COMBO_BOX(cbStrokeType), cfg->strokeType);
if (withDevice) {
gtk_combo_box_set_active(GTK_COMBO_BOX(cbDevice), 0);
int i = 1;
for (InputDevice const& dev: this->deviceList) {
if (cfg->device == dev.getName()) {
gtk_combo_box_set_active(GTK_COMBO_BOX(cbDevice), i);
break;
}
++i;
}
gtk_check_button_set_active(GTK_CHECK_BUTTON(cbDisableDrawing), cfg->disableDrawing);
}
}
void ButtonConfigGui::saveSettings() {
GtkTreeIter iter;
gtk_combo_box_get_active_iter(GTK_COMBO_BOX(cbTool), &iter);
GValue value = {0};
GtkTreeModel* model = gtk_combo_box_get_model(GTK_COMBO_BOX(cbTool));
gtk_tree_model_get_value(model, &iter, 2, &value);
auto action = static_cast<ToolType>(g_value_get_int(&value));
ButtonConfig* cfg = settings->getButtonConfig(button);
cfg->action = action;
int thickness = gtk_combo_box_get_active(GTK_COMBO_BOX(cbThickness));
if (toolSizeIndexMap.count(thickness) == 0) {
cfg->size = TOOL_SIZE_NONE;
} else {
cfg->size = toolSizeIndexMap[thickness];
}
GdkRGBA color;
gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(colorButton), &color);
cfg->color = Util::GdkRGBA_to_argb(color);
cfg->drawingType = static_cast<DrawingType>(gtk_combo_box_get_active(GTK_COMBO_BOX(this->cbDrawingType)));
int eraserMode = gtk_combo_box_get_active(GTK_COMBO_BOX(this->cbEraserType));
if (eraserMode == 1) {
cfg->eraserMode = ERASER_TYPE_DEFAULT;
} else if (eraserMode == 2) {
cfg->eraserMode = ERASER_TYPE_WHITEOUT;
} else if (eraserMode == 3) {
cfg->eraserMode = ERASER_TYPE_DELETE_STROKE;
} else {
cfg->eraserMode = ERASER_TYPE_NONE;
}
cfg->strokeType = static_cast<StrokeType>(gtk_combo_box_get_active(GTK_COMBO_BOX(this->cbStrokeType)));
if (this->withDevice) {
size_t dev = static_cast<size_t>(gtk_combo_box_get_active(GTK_COMBO_BOX(cbDevice)) - 1);
cfg->device = (dev < 0 || this->deviceList.size() <= dev) ? "" : this->deviceList[dev].getName();
cfg->disableDrawing = gtk_check_button_get_active(GTK_CHECK_BUTTON(cbDisableDrawing));
}
settings->customSettingsChanged();
}
void ButtonConfigGui::cbSelectCallback(GtkComboBox*, ButtonConfigGui* gui) { gui->enableDisableTools(); }
void ButtonConfigGui::enableDisableTools() {
GtkTreeIter iter;
gtk_combo_box_get_active_iter(GTK_COMBO_BOX(cbTool), &iter);
GtkTreeModel* model = gtk_combo_box_get_model(GTK_COMBO_BOX(cbTool));
GValue value = {0};
gtk_tree_model_get_value(model, &iter, 2, &value);
auto action = static_cast<ToolType>(g_value_get_int(&value));
gtk_widget_set_visible(cbThickness, action != TOOL_NONE && xoj::tool::hasCapability(action, TOOL_CAP_SIZE));
gtk_widget_set_visible(colorButton, action != TOOL_NONE && xoj::tool::hasCapability(action, TOOL_CAP_COLOR));
gtk_widget_set_visible(cbDrawingType, action != TOOL_NONE && xoj::tool::hasCapability(action, TOOL_CAP_RECTANGLE));
gtk_widget_set_visible(cbEraserType, action == TOOL_ERASER);
gtk_widget_set_visible(cbStrokeType, action == TOOL_PEN);
}
@@ -1,62 +0,0 @@
/*
* Xournal++
*
* The configuration for a button in the Settings Dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <map> // for map, map<>::value_compare
#include <string> // for string
#include <vector> // for vector
#include <gtk/gtk.h> // for GtkWidget, GtkComboBox, GtkWindow
#include "control/DeviceListHelper.h" // for InputDevice
#include "control/ToolEnums.h" // for ToolSize
#include "gui/IconNameHelper.h" // for IconNameHelper
class Settings;
class GladeSearchpath;
class ButtonConfigGui {
public:
ButtonConfigGui(GladeSearchpath* gladeSearchPath, GtkBox* box, Settings* settings, unsigned int button,
bool withDevice);
public:
void loadSettings();
void saveSettings();
private:
static void cbSelectCallback(GtkComboBox* widget, ButtonConfigGui* gui);
void enableDisableTools();
std::string toolSizeToLabel(ToolSize size);
private:
Settings* settings;
GtkWidget* cbDevice;
GtkWidget* cbDisableDrawing;
GtkWidget* cbTool;
GtkWidget* cbThickness;
GtkWidget* colorButton;
GtkWidget* cbEraserType;
GtkWidget* cbDrawingType;
GtkWidget* cbStrokeType;
std::vector<InputDevice> deviceList;
unsigned int button = 0;
bool withDevice = false;
typedef std::map<int, ToolSize> ToolSizeIndexMap;
static ToolSizeIndexMap toolSizeIndexMap;
IconNameHelper iconNameHelper;
};
@@ -1,61 +0,0 @@
#include "DeviceClassConfigGui.h"
#include <string> // for allocator, char_traits
#include <glib.h> // for g_ascii_strtoll, g_strdu...
#include "control/DeviceListHelper.h" // for InputDevice
#include "control/settings/Settings.h" // for Settings
#include "control/settings/SettingsEnums.h" // for InputDeviceTypeOption
#include "gui/Builder.h" // for Builder
#include "util/Assert.h" // for xoj_assert
#include "util/gtk4_helper.h" // for gtk_box_append
class GladeSearchpath;
constexpr auto UI_FILE = "settingsDeviceClassConfig.glade";
constexpr auto UI_WIDGET_NAME = "deviceClassBox";
DeviceClassConfigGui::DeviceClassConfigGui(GladeSearchpath* gladeSearchPath, GtkBox* box, Settings* settings,
bool showApplyBtn):
settings(settings) {
Builder builder(gladeSearchPath, UI_FILE);
gtk_box_append(box, builder.get(UI_WIDGET_NAME)); // box takes ownership of it all!
this->labelDevice = builder.get("labelDevice");
this->cbDeviceClass = builder.get("cbDeviceClass");
auto* applyBtn = builder.get("btnApply");
gtk_widget_set_visible(applyBtn, showApplyBtn);
if (showApplyBtn) {
g_signal_connect(applyBtn, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) {
static_cast<DeviceClassConfigGui*>(d)->saveSettings();
}),
this);
}
}
DeviceClassConfigGui::~DeviceClassConfigGui() = default;
void DeviceClassConfigGui::setDevice(const InputDevice& d) {
this->device = d;
gtk_label_set_text(GTK_LABEL(this->labelDevice), (device.getName() + " (" + device.getType() + ")").c_str());
loadSettings();
}
void DeviceClassConfigGui::loadSettings() {
// Get device class of device if available or
InputDeviceTypeOption deviceType =
this->settings->getDeviceClassForDevice(this->device.getName(), this->device.getSource());
// Use the ID of each option in case the combo box options get rearranged in the future
gtk_combo_box_set_active_id(GTK_COMBO_BOX(this->cbDeviceClass),
std::to_string(static_cast<int>(deviceType)).c_str());
}
void DeviceClassConfigGui::saveSettings() {
const gchar* deviceClassId = gtk_combo_box_get_active_id(GTK_COMBO_BOX(this->cbDeviceClass));
xoj_assert(deviceClassId != nullptr);
auto deviceClass = static_cast<InputDeviceTypeOption>(g_ascii_strtoll(deviceClassId, nullptr, 10));
this->settings->setDeviceClassForDevice(this->device.getName(), this->device.getSource(), deviceClass);
}
@@ -1,42 +0,0 @@
/*
* Xournal++
*
* The configuration for a button in the Settings Dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <gtk/gtk.h> // for GtkWidget, GtkComboBox, GtkWindow
#include "control/DeviceListHelper.h" // for InputDevice
class Settings;
class GladeSearchpath;
class DeviceClassConfigGui {
public:
DeviceClassConfigGui(GladeSearchpath* gladeSearchPath, GtkBox* box, Settings* settings, bool showApplyBtn = false);
~DeviceClassConfigGui();
public:
void loadSettings();
void saveSettings();
/// Set the device represented by the entry.
/// Please call saveSettings() beforehand if the settings of the previous device need to be saved.
void setDevice(const InputDevice& device);
inline bool representsDevice(const InputDevice& dev) const { return dev == device; }
private:
Settings* settings;
InputDevice device;
GtkWidget* labelDevice;
GtkWidget* cbDeviceClass;
};
@@ -1,39 +0,0 @@
#include "DeviceListDialog.h"
#include "control/settings/Settings.h"
#include "gui/Builder.h"
#include "util/gtk4_helper.h" // for gtk_box_append, ...
#include "DeviceClassConfigGui.h"
constexpr auto UI_FILE_NAME = "deviceList.glade";
constexpr auto UI_MAIN_ID = "deviceListDialog";
constexpr auto UI_CONTENT_BOX = "contentBox";
DeviceListDialog::DeviceListDialog(GladeSearchpath* gladeSearchPath, Settings* settings): settings(settings) {
Builder builder(gladeSearchPath, UI_FILE_NAME);
window.reset(GTK_WINDOW(builder.get(UI_MAIN_ID)));
auto deviceList = DeviceListHelper::getDeviceList(settings);
auto* box = GTK_BOX(builder.get(UI_CONTENT_BOX));
for (const InputDevice& inputDevice: deviceList) {
auto& entry = this->deviceClassConfigs.emplace_back(gladeSearchPath, box, settings);
entry.setDevice(inputDevice);
}
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(+[](DeviceListDialog* self) {
self->saveSettings();
gtk_window_close(self->window.get());
}),
this);
}
void DeviceListDialog::saveSettings() {
this->settings->transactionStart();
for (auto& deviceClassConfigGui: this->deviceClassConfigs) {
deviceClassConfigGui.saveSettings();
}
this->settings->transactionEnd();
}
@@ -1,37 +0,0 @@
/*
* Xournal++
*
* Dialog listing known devices
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <vector> // for vector
#include "util/raii/GtkWindowUPtr.h"
class Settings;
class GladeSearchpath;
class DeviceClassConfigGui;
class DeviceListDialog {
public:
DeviceListDialog(GladeSearchpath* gladeSearchPath, Settings* settings);
public:
inline GtkWindow* getWindow() const { return window.get(); }
private:
void saveSettings();
private:
Settings* settings;
std::vector<DeviceClassConfigGui> deviceClassConfigs;
xoj::util::GtkWindowUPtr window;
};
@@ -1,425 +0,0 @@
#include "DeviceTestingArea.h"
#include <bitset>
#include <sstream>
#include <config-git.h>
#include <config.h>
#include <zip.h>
#include "control/settings/Settings.h"
#include "gui/Builder.h"
#include "gui/dialog/XojSaveDlg.h"
#include "gui/inputdevices/InputContext.h"
#include "gui/inputdevices/InputEvents.h"
#include "gui/inputdevices/PrintEvent.h"
#include "model/Point.h"
#include "util/CircularBuffer.h"
#include "util/EnumIndexedArray.h"
#include "util/PathUtil.h"
#include "util/PopupWindowWrapper.h"
#include "util/VersionInfo.h"
#include "util/glib_casts.h"
#include "util/gtk4_helper.h" // for gtk_box_append, ...
#include "util/i18n.h"
#include "util/raii/GtkWindowUPtr.h"
#include "DeviceClassConfigGui.h"
#include "DeviceListDialog.h"
#include "filesystem.h"
constexpr auto UI_FILE_NAME = "deviceTestingArea.glade";
constexpr auto UI_MAIN_ID = "mainBox";
static constexpr double PRESSURE_FACTOR = 20;
static constexpr size_t CONTACT_BUTTON_INDEX = 1; ///< Corresponds to button == 1 in GdkEvent
static constexpr double PRESSURELESS_INDICATOR_HALF_SIZE = 3;
static constexpr size_t MAX_NB_BUTTONS = 4;
static constexpr std::bitset<MAX_NB_BUTTONS> CONSIDERED_FOR_TIP_EMULATION(0b1100);
static constexpr std::bitset<MAX_NB_BUTTONS> MOUSE_BUTTONS_MASK(0b1110);
static constexpr unsigned int IN_USE_RESET_DELAY = 100; ///< in ms
struct Ev {
Point point;
std::bitset<MAX_NB_BUTTONS> pressedButtons;
DeviceTestingArea::HandlerType type;
};
enum class DevCategory : size_t {
MOUSE = 0,
STYLUS = 1,
ERASER = 2,
TOUCHSCREEN = 3,
ENUMERATOR_COUNT = 4 ///< Used as bound
}; /// Used as indices for buttonsStatus
struct DeviceTestingArea::Data {
CircularBuffer<Ev> lastEvs{20};
EnumIndexedArray<std::bitset<MAX_NB_BUTTONS>, DevCategory> buttonsStatus;
size_t nbTouchSequences;
std::stringstream log; ///< all the events as received (after conversion) by the handlers
std::stringstream ellipsizedLog; ///< Same as log, but sequences of MOTION_EVENT's are ellipsized
std::stringstream allGdkEvents; ///< all the events, as emitted by GDK
size_t nbOfConsecutiveMotionEvents = 0;
GdkDevice* lastMotionDevice = nullptr;
guint32 timeReference = 0; ///< timestamp of DeviceTestingArea's creation
};
static void draw(GtkDrawingArea*, cairo_t* cr, int width, int height, gpointer lastEvs) {
cairo_set_source_rgb(cr, 1., 1., 1.);
cairo_paint(cr);
cairo_set_source_rgb(cr, 0., 0., 0.);
cairo_set_line_width(cr, 2);
// Draw a frame - using CSS seems not to work for GtkDrawingArea's
cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
cairo_line_to(cr, 0., 0.);
cairo_line_to(cr, width, 0.);
cairo_line_to(cr, width, height);
cairo_line_to(cr, 0., height);
cairo_close_path(cr);
cairo_stroke(cr);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
for (auto&& e: *static_cast<CircularBuffer<Ev>*>(lastEvs)) {
if (e.pressedButtons[CONTACT_BUTTON_INDEX] ||
(e.type == DeviceTestingArea::MOUSE && (e.pressedButtons & MOUSE_BUTTONS_MASK).any())) {
// Contact
if (e.point.z == Point::NO_PRESSURE) {
cairo_set_source_rgb(cr, 1., 0., 0.);
cairo_move_to(cr, e.point.x, e.point.y);
cairo_rel_move_to(cr, -PRESSURELESS_INDICATOR_HALF_SIZE, -PRESSURELESS_INDICATOR_HALF_SIZE);
cairo_rel_line_to(cr, 2 * PRESSURELESS_INDICATOR_HALF_SIZE, 2 * PRESSURELESS_INDICATOR_HALF_SIZE);
cairo_rel_move_to(cr, -2 * PRESSURELESS_INDICATOR_HALF_SIZE, 0);
cairo_rel_line_to(cr, 2 * PRESSURELESS_INDICATOR_HALF_SIZE, -2 * PRESSURELESS_INDICATOR_HALF_SIZE);
} else {
cairo_set_source_rgb(cr, .4, 1., 0.);
cairo_arc(cr, e.point.x, e.point.y, PRESSURE_FACTOR * e.point.z, 0, 2. * M_PI);
}
} else {
// No contact: hovering
cairo_set_source_rgb(cr, .5, .5, .5);
cairo_move_to(cr, e.point.x, e.point.y);
cairo_rel_line_to(cr, 0, 0);
}
cairo_stroke(cr);
}
}
static void saveLog(const std::stringstream& fullLog, const std::stringstream& ellipsizedLog,
const std::stringstream& gdkeventslog, GtkWidget* parent, Settings* settings) {
auto pathValidation = [](fs::path& p, const char* filterName) {
Util::clearExtensions(p, ".zip");
p += ".zip";
return true;
};
auto callback = [fullLog = fullLog.str(), ellipsizedLog = ellipsizedLog.str(), gdkeventslog = gdkeventslog.str(),
settings = settings->getSettingsFile()](std::optional<fs::path> p) {
if (p && !p->empty()) {
int errorCode = 0;
auto* zip = zip_open(char_cast(p->u8string().c_str()), ZIP_CREATE | ZIP_TRUNCATE, &errorCode);
if (!zip) {
// TODO handle error
// errorCode contains the error
return;
}
auto addSource = [&](const char* name, zip_source_t* source) {
if (!source) {
// TODO handle error
// zip_strerror(zip) returns the error string
} else if (zip_file_add(zip, name, source, ZIP_FL_ENC_UTF_8) < 0) {
// TODO handler error
// zip_strerror(zip) returns the error string
zip_source_free(source);
}
};
addSource("ellipsized-logs.txt", zip_source_buffer(zip, ellipsizedLog.data(), ellipsizedLog.length(), 0));
addSource("full-logs.txt", zip_source_buffer(zip, fullLog.data(), fullLog.length(), 0));
addSource("input-gdk_events.txt", zip_source_buffer(zip, gdkeventslog.data(), gdkeventslog.length(), 0));
#ifdef ZIP_LENGTH_TO_END // Only introduced in libzip 1.10.1 - ubuntu 22 has v1.7.3
addSource("settings.xml",
zip_source_file(zip, char_cast(settings.u8string().c_str()), 0, ZIP_LENGTH_TO_END));
#else
addSource("settings.xml", zip_source_file(zip, char_cast(settings.u8string().c_str()), 0, -1));
#endif
auto version = xoj::util::getVersionInfo();
addSource("version.txt", zip_source_buffer(zip, version.data(), version.length(), 0));
errorCode = zip_close(zip);
if (errorCode) {
// TODO handle error
}
}
};
auto popup = xoj::popup::PopupWindowWrapper<xoj::SaveExportDialog>(
nullptr, fs::path(), _("Export Logs"), _("Export"), std::move(pathValidation), std::move(callback));
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
GtkFileFilter* filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, "ZIP archive");
gtk_file_filter_add_mime_type(filter, "application/zip");
gtk_file_chooser_add_filter(fc, filter);
popup.show(GTK_WINDOW(gtk_widget_get_ancestor(parent, GTK_TYPE_WINDOW)));
}
DeviceTestingArea::DeviceTestingArea(GladeSearchpath* gladeSearchPath, GtkBox* parent, Settings* settings):
settings(settings),
gladeSearchPath(gladeSearchPath),
emulateTipContactOnButtonPress(settings->getInputSystemTPCButtonEnabled()),
inputContext(std::make_unique<InputContext>(settings, *this)),
ancestorScrolledWindow(gtk_widget_get_ancestor(GTK_WIDGET(parent), GTK_TYPE_SCROLLED_WINDOW), xoj::util::ref),
data(std::make_unique<Data>()) {
data->log << std::setprecision(6);
data->log.imbue(std::locale::classic());
data->allGdkEvents << std::setprecision(6);
data->allGdkEvents.imbue(std::locale::classic());
data->ellipsizedLog << std::setprecision(6);
data->ellipsizedLog.imbue(std::locale::classic());
data->timeReference = strict_cast<guint32>(as_unsigned(g_get_monotonic_time()) / 1000);
Builder builder(gladeSearchPath, UI_FILE_NAME);
gtk_box_append(parent, builder.get(UI_MAIN_ID));
struct D {
std::stringstream* log;
GtkTextBuffer* view;
};
g_signal_connect_data(builder.get("logpopover"), "show", G_CALLBACK(+[](GtkWidget*, gpointer d) {
auto* dd = static_cast<D*>(d);
gtk_text_buffer_set_text(dd->view, dd->log->str().c_str(), -1);
}),
new D{&data->ellipsizedLog, GTK_TEXT_BUFFER(builder.get<GObject>("logbuffer"))},
xoj::util::closure_notify_cb<D>, GConnectFlags(0));
auto* cbEmulateTipContactOnButtonPress = builder.get("cbEmulateTipContactOnButtonPress");
gtk_check_button_set_active(GTK_CHECK_BUTTON(cbEmulateTipContactOnButtonPress), emulateTipContactOnButtonPress);
g_signal_connect(cbEmulateTipContactOnButtonPress, "toggled",
G_CALLBACK(+[]
#if GTK_MAJOR_VERSION == 3
(GtkToggleButton* btn, gpointer d) {
static_cast<DeviceTestingArea*>(d)->emulateTipContactOnButtonPress =
gtk_toggle_button_get_active(btn);
}),
#else
(GtkCheckButton* btn, gpointer d) {
static_cast<DeviceTestingArea*>(d)->emulateTipContactOnButtonPress =
gtk_check_button_get_active(btn);
}),
#endif
this);
g_signal_connect(builder.get("btnExportLogs"), "clicked", G_CALLBACK(+[](GtkButton* btn, gpointer d) {
auto* self = static_cast<DeviceTestingArea*>(d);
saveLog(self->data->log, self->data->ellipsizedLog, self->data->allGdkEvents, GTK_WIDGET(btn),
self->settings);
}),
this);
g_signal_connect(builder.get("btnAllDevices"), "clicked", G_CALLBACK(+[](GtkButton* btn, gpointer d) {
auto* self = static_cast<DeviceTestingArea*>(d);
auto dlg = xoj::popup::PopupWindowWrapper<DeviceListDialog>(self->gladeSearchPath,
self->settings);
dlg.show(GTK_WINDOW(gtk_widget_get_ancestor(GTK_WIDGET(btn), GTK_TYPE_WINDOW)));
}),
this);
lastDeviceClassConfig = std::make_unique<DeviceClassConfigGui>(
gladeSearchPath, GTK_BOX(builder.get("boxLastDevice")), settings, true);
drawingArea.reset(builder.get("testArea"), xoj::util::ref);
mouseIndicators[0].reset(builder.get("mouse-in-use"), xoj::util::ref);
mouseIndicators[GDK_BUTTON_PRIMARY].reset(builder.get("mouse-left"), xoj::util::ref);
mouseIndicators[GDK_BUTTON_MIDDLE].reset(builder.get("mouse-middle"), xoj::util::ref);
mouseIndicators[GDK_BUTTON_SECONDARY].reset(builder.get("mouse-right"), xoj::util::ref);
stylusIndicators[0].reset(builder.get("stylus-hover"), xoj::util::ref);
stylusIndicators[1].reset(builder.get("stylus-tip"), xoj::util::ref);
stylusIndicators[2].reset(builder.get("stylus-1"), xoj::util::ref);
stylusIndicators[3].reset(builder.get("stylus-2"), xoj::util::ref);
eraserIndicators[0].reset(builder.get("eraser-hover"), xoj::util::ref);
eraserIndicators[1].reset(builder.get("eraser-tip"), xoj::util::ref);
touchIndicators[0].reset(builder.get("touch-1"), xoj::util::ref);
touchIndicators[1].reset(builder.get("touch-2"), xoj::util::ref);
touchIndicators[2].reset(builder.get("touch-3"), xoj::util::ref);
touchIndicators[3].reset(builder.get("touch-4"), xoj::util::ref);
touchIndicators[4].reset(builder.get("touch-5"), xoj::util::ref);
inputContext->connect(drawingArea.get(), /* connectKeyboardHandler */ false, [data = data.get()](GdkEvent* e) {
xoj::input::printGdkEvent(data->allGdkEvents, e, data->timeReference);
});
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(drawingArea.get()), draw, &data->lastEvs, nullptr);
}
DeviceTestingArea::~DeviceTestingArea() = default;
static constexpr const char* handlerTypeToString[] = {"MOUSE", "STYLUS", "TOUCH"};
bool DeviceTestingArea::handle(const InputEvent& e, HandlerType handlerType) {
if (e.deviceClass == INPUT_DEVICE_TOUCHSCREEN) {
/*
* kinetic scrolling will capture touch event if the sequence looks like a scroll.
* We do not want to scroll when testing touch devices in the testing area, so we disable it
*/
if (e.type == ENTER_EVENT) {
gtk_scrolled_window_set_kinetic_scrolling(GTK_SCROLLED_WINDOW(ancestorScrolledWindow.get()), false);
} else if (e.type == LEAVE_EVENT) {
gtk_scrolled_window_set_kinetic_scrolling(GTK_SCROLLED_WINDOW(ancestorScrolledWindow.get()), true);
}
}
data->log << "Handler " << handlerTypeToString[handlerType] << " received:\n";
xoj::input::printEvent(data->log, e, data->timeReference);
if (e.type != MOTION_EVENT || e.device != data->lastMotionDevice) {
if (data->nbOfConsecutiveMotionEvents > 1) {
data->ellipsizedLog << " ⦙\n " << data->nbOfConsecutiveMotionEvents - 1
<< " motion events omitted\n ⦙\n";
}
if (e.type == MOTION_EVENT) {
data->nbOfConsecutiveMotionEvents = 1;
data->lastMotionDevice = e.device;
} else {
data->nbOfConsecutiveMotionEvents = 0;
data->lastMotionDevice = nullptr;
}
data->ellipsizedLog << "Handler " << handlerTypeToString[handlerType] << " received:\n";
xoj::input::printEvent(data->ellipsizedLog, e, data->timeReference);
} else {
if (data->nbOfConsecutiveMotionEvents == 0) {
data->ellipsizedLog << "Handler " << handlerTypeToString[handlerType] << " received:\n";
xoj::input::printEvent(data->ellipsizedLog, e, data->timeReference);
data->lastMotionDevice = e.device;
}
data->nbOfConsecutiveMotionEvents++;
}
auto queueEvent = [&]() {
auto category = [&]() {
switch (e.deviceClass) {
case INPUT_DEVICE_MOUSE:
return DevCategory::MOUSE;
case INPUT_DEVICE_PEN:
return DevCategory::STYLUS;
case INPUT_DEVICE_ERASER:
return DevCategory::ERASER;
case INPUT_DEVICE_TOUCHSCREEN:
return DevCategory::TOUCHSCREEN;
default:
return DevCategory::MOUSE;
}
}();
data->lastEvs.push_front(
Ev{Point(e.relative.x, e.relative.y, e.pressure), data->buttonsStatus[category], handlerType});
gtk_widget_queue_draw(drawingArea.get());
};
// The mouse rarely issues LEAVE_EVENT. We turn off the "in-use" indicator after a short time of inactivity
auto resetInUseTimer = [](xoj::util::GSourceURef& timer, GtkWidget* indicator) {
if (!timer) {
gtk_widget_add_css_class(indicator, "pressed");
}
struct cbData {
GtkWidget* indicator;
xoj::util::GSourceURef* timer;
};
timer = g_timeout_add_full(
G_PRIORITY_DEFAULT, IN_USE_RESET_DELAY,
+[](gpointer d) {
auto* data = static_cast<cbData*>(d);
gtk_widget_remove_css_class(data->indicator, "pressed");
data->timer->consume();
return G_SOURCE_REMOVE;
},
new cbData{indicator, &timer}, xoj::util::destroy_cb<cbData>);
};
if (e.type == BUTTON_PRESS_EVENT) {
lastDeviceClassConfig->setDevice(InputDevice(e.device));
if (handlerType == MOUSE && e.button < mouseIndicators.size()) {
data->buttonsStatus[DevCategory::MOUSE].set(e.button);
gtk_widget_add_css_class(mouseIndicators[e.button].get(), "pressed");
resetInUseTimer(mouseInUseTimer, mouseIndicators[0].get());
} else if (handlerType == STYLUS) {
if (e.deviceClass == INPUT_DEVICE_PEN && e.button < stylusIndicators.size()) {
data->buttonsStatus[DevCategory::STYLUS].set(e.button);
gtk_widget_add_css_class(stylusIndicators[e.button].get(), "pressed");
resetInUseTimer(stylusInUseTimer, stylusIndicators[0].get());
if (e.button != CONTACT_BUTTON_INDEX && emulateTipContactOnButtonPress &&
CONSIDERED_FOR_TIP_EMULATION[e.button]) {
data->buttonsStatus[DevCategory::STYLUS].set(CONTACT_BUTTON_INDEX);
gtk_widget_add_css_class(stylusIndicators[CONTACT_BUTTON_INDEX].get(), "pressed");
}
} else if (e.deviceClass == INPUT_DEVICE_ERASER && e.button < eraserIndicators.size()) {
data->buttonsStatus[DevCategory::ERASER].set(e.button);
gtk_widget_add_css_class(eraserIndicators[e.button].get(), "pressed");
resetInUseTimer(eraserInUseTimer, eraserIndicators[0].get());
}
} else if (handlerType == TOUCH) {
data->nbTouchSequences++;
if (data->nbTouchSequences <= 5) {
gtk_widget_add_css_class(touchIndicators[data->nbTouchSequences - 1].get(), "pressed");
}
data->buttonsStatus[DevCategory::TOUCHSCREEN].set(CONTACT_BUTTON_INDEX);
}
queueEvent();
} else if (e.type == BUTTON_RELEASE_EVENT) {
if (handlerType == MOUSE && e.button < mouseIndicators.size()) {
data->buttonsStatus[DevCategory::MOUSE].reset(e.button);
gtk_widget_remove_css_class(mouseIndicators[e.button].get(), "pressed");
resetInUseTimer(mouseInUseTimer, mouseIndicators[0].get());
} else if (handlerType == TOUCH) {
if (data->nbTouchSequences == 0) {
data->log << "ERROR: Terminating more touch sequences than had been started" << std::endl;
} else if (data->nbTouchSequences <= 5) {
gtk_widget_remove_css_class(touchIndicators[data->nbTouchSequences - 1].get(), "pressed");
if (--data->nbTouchSequences == 0) {
data->buttonsStatus[DevCategory::TOUCHSCREEN].reset(0);
}
}
} else {
if (e.deviceClass == INPUT_DEVICE_PEN && e.button < stylusIndicators.size()) {
data->buttonsStatus[DevCategory::STYLUS].reset(e.button);
gtk_widget_remove_css_class(stylusIndicators[e.button].get(), "pressed");
resetInUseTimer(stylusInUseTimer, stylusIndicators[0].get());
if (e.button != CONTACT_BUTTON_INDEX && emulateTipContactOnButtonPress) {
data->buttonsStatus[DevCategory::STYLUS].reset(CONTACT_BUTTON_INDEX);
gtk_widget_remove_css_class(stylusIndicators[CONTACT_BUTTON_INDEX].get(), "pressed");
}
} else if (e.deviceClass == INPUT_DEVICE_ERASER && e.button < eraserIndicators.size()) {
data->buttonsStatus[DevCategory::ERASER].reset(e.button);
gtk_widget_remove_css_class(eraserIndicators[e.button].get(), "pressed");
resetInUseTimer(eraserInUseTimer, eraserIndicators[0].get());
}
}
queueEvent();
} else if (e.type == MOTION_EVENT) {
if (handlerType == MOUSE) {
resetInUseTimer(mouseInUseTimer, mouseIndicators[0].get());
} else if (handlerType == STYLUS) {
if (e.deviceClass == INPUT_DEVICE_PEN) {
resetInUseTimer(stylusInUseTimer, stylusIndicators[0].get());
} else {
resetInUseTimer(eraserInUseTimer, eraserIndicators[0].get());
}
}
queueEvent();
}
return true;
}
void DeviceTestingArea::saveSettings() const {
this->settings->setInputSystemTPCButtonEnabled(this->emulateTipContactOnButtonPress);
this->lastDeviceClassConfig->saveSettings();
}
@@ -1,82 +0,0 @@
/*
* Xournal++
*
* An area to test input devices
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <array>
#include <memory>
#include <vector>
#include <gtk/gtk.h> // for GtkWidget, ...
#include "util/raii/GObjectSPtr.h"
#include "util/raii/GSourceURef.h"
class Settings;
class InputContext;
struct InputEvent;
class DeviceClassConfigGui;
class GladeSearchpath;
class DeviceTestingArea {
public:
DeviceTestingArea(GladeSearchpath* path, GtkBox* parent, Settings* settings);
~DeviceTestingArea();
enum HandlerType { MOUSE = 0, STYLUS = 1, TOUCH = 2 };
bool handle(const InputEvent& e, HandlerType t);
void saveSettings() const;
private:
Settings* settings;
GladeSearchpath* gladeSearchPath;
bool emulateTipContactOnButtonPress;
std::unique_ptr<InputContext> inputContext;
xoj::util::WidgetSPtr drawingArea;
/// Indicators for button status
std::array<xoj::util::WidgetSPtr, 4> mouseIndicators; ///< 0 = in use, 1 = left, 2 = middle, 3 = right
std::array<xoj::util::WidgetSPtr, 4> stylusIndicators; ///< 0 = in use, 1 = tip, 2 = button-1, 3 = button-2
std::array<xoj::util::WidgetSPtr, 2> eraserIndicators; ///< 0 = in use, 1 = tip
std::array<xoj::util::WidgetSPtr, 5> touchIndicators;
/// Mouse "in use" timer: the mouse stays in use 100ms after its last event
xoj::util::GSourceURef mouseInUseTimer;
xoj::util::GSourceURef stylusInUseTimer;
xoj::util::GSourceURef eraserInUseTimer;
/// Used to disable kinetic scrolling in order to properly test touchscreens
xoj::util::WidgetSPtr ancestorScrolledWindow;
/// Show and edit the configuration of the last device used
std::unique_ptr<DeviceClassConfigGui> lastDeviceClassConfig;
struct Data;
std::unique_ptr<Data> data;
};
template <typename Handler>
class TestingHandler: public Handler {
public:
explicit TestingHandler(InputContext* context, DeviceTestingArea::HandlerType t, DeviceTestingArea& testing):
Handler(context), deviceType(t), testing(testing) {}
~TestingHandler() = default;
bool handleImpl(const InputEvent& e) override { return testing.handle(e, deviceType); }
private:
const DeviceTestingArea::HandlerType deviceType;
DeviceTestingArea& testing;
};
@@ -1,192 +0,0 @@
#include "ExportDialog.h"
#include <algorithm> // for max
#include <stdexcept> // for invalid_argument
#include <string> // for string, to_string, operator+
#include <vector> // for allocator
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
#include "gui/Builder.h"
#include "util/ElementRange.h" // for parse, PageRangeVector
#include "util/gtk4_helper.h"
class GladeSearchpath;
constexpr auto UI_FILE = "exportSettings.glade";
constexpr auto UI_DIALOG_NAME = "exportDialog";
using namespace xoj::popup;
ExportDialog::ExportDialog(GladeSearchpath* gladeSearchPath, ExportGraphicsFormat format, size_t currentPage,
size_t pageCount, bool hasPdfBackground,
std::function<void(const ExportDialog&)> callbackFun):
currentPage(currentPage), pageCount(pageCount), builder(gladeSearchPath, UI_FILE), callbackFun(callbackFun) {
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
gtk_label_set_text(GTK_LABEL(builder.get("lbAllPagesInfo")), ("1 - " + std::to_string(pageCount)).c_str());
gtk_label_set_text(GTK_LABEL(builder.get("lbCurrentPage")), std::to_string(currentPage).c_str());
#if GTK_MAJOR_VERSION == 3
// Widgets are visible by default in gtk4
gtk_widget_show_all(builder.get("dialog-main-box"));
#endif
auto removeQualitySetting = [&builder = this->builder]() {
gtk_widget_hide(builder.get("lbQuality"));
gtk_widget_hide(builder.get("boxQuality"));
gtk_widget_hide(builder.get("cbQuality"));
};
if (format == EXPORT_GRAPHICS_PDF) {
removeQualitySetting();
auto* cbt = GTK_COMBO_BOX_TEXT(builder.get("cbtPdfExportBackend"));
for (auto&& [id, name]: ExportBackend::getPrettyNamesOfAvailableBackends()) {
gtk_combo_box_text_append(cbt, id, name);
}
gtk_combo_box_set_active_id(GTK_COMBO_BOX(cbt), ExportBackend::DEFAULT_ID_STRING);
if (!hasPdfBackground) {
// No need for backend selection if no pdf background is present.
gtk_widget_set_sensitive(builder.get("boxPdfBackend"), false);
}
} else if (format == EXPORT_GRAPHICS_PNG) {
gtk_widget_hide(builder.get("cbProgressiveMode"));
gtk_widget_hide(builder.get("boxPdfBackend"));
} else { // (format == EXPORT_GRAPHICS_SVG)
removeQualitySetting();
gtk_widget_hide(builder.get("cbProgressiveMode"));
gtk_widget_hide(builder.get("boxPdfBackend"));
}
// rdRangePages toggled signal handler
//
// Sets and unsets the sensitivity of the text form, OK button and
// displays an error when the text form is selected and contains
// invalid user data. The error goes away when the text form is de-selected.
auto toggledHandler = G_CALLBACK(+[](GtkCheckButton* button, ExportDialog* self) {
auto active = gtk_check_button_get_active(button);
auto btOk = self->builder.get("btOk");
auto txtPages = self->builder.get("txtPages");
gtk_widget_set_sensitive(txtPages, active);
if (active) {
const std::string text_form(gtk_editable_get_text(GTK_EDITABLE(txtPages)));
try {
ElementRange::parse(text_form, self->pageCount);
gtk_widget_remove_css_class(txtPages, "error");
gtk_widget_set_sensitive(btOk, true);
} catch (const std::invalid_argument&) {
gtk_widget_add_css_class(txtPages, "error");
gtk_widget_set_sensitive(btOk, false);
}
} else {
gtk_widget_remove_css_class(txtPages, "error");
gtk_widget_set_sensitive(btOk, true);
}
});
// txtPages changed signal handler
//
// Displays an error when the user inputs invalid page ranges in the text form,
// and sets the sensitivity of the OK button to FALSE. These actions are
// reversed when the text form contains a valid page range.
auto changedHandler = G_CALLBACK(+[](GtkEditable* txtPages, ExportDialog* self) {
const std::string text_form(gtk_editable_get_text(txtPages));
auto btOk = self->builder.get("btOk");
try {
ElementRange::parse(text_form, self->pageCount);
gtk_widget_remove_css_class(GTK_WIDGET(txtPages), "error");
gtk_widget_set_sensitive(btOk, true);
} catch (const std::invalid_argument& e) {
gtk_widget_add_css_class(GTK_WIDGET(txtPages), "error");
gtk_widget_set_sensitive(btOk, false);
}
});
g_signal_connect(builder.get("rdRangePages"), "toggled", toggledHandler, this);
g_signal_connect(builder.get("cbQuality"), "changed", G_CALLBACK(ExportDialog::selectQualityCriterion), this);
g_signal_connect(builder.get("txtPages"), "changed", changedHandler, this);
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(ExportDialog::onSuccessCallback), this);
/**
* By calling this->callbackFun() here, we make sure that `control->unblock()` is run even if the user clicks on
* the close-window button.
*
* The callback returns `false` so that the PopupWindowManager callback deleting `this` gets called as well.
*/
#if GTK_MAJOR_VERSION == 3
g_signal_connect_swapped(window.get(), "delete-event", G_CALLBACK(+[](ExportDialog* self) {
self->callbackFun(*self);
return false;
}),
this);
#else
g_signal_connect_swapped(window.get(), "close-request", G_CALLBACK(+[](ExportDialog* self) {
self->callbackFun(*self);
return false;
}),
this);
#endif
}
ExportDialog::~ExportDialog() = default;
void ExportDialog::selectQualityCriterion(GtkComboBox* comboBox, ExportDialog* self) {
int activeCriterion = gtk_combo_box_get_active(comboBox);
switch (activeCriterion) {
case EXPORT_QUALITY_DPI:
gtk_label_set_text(GTK_LABEL(self->builder.get("lbQualityUnit")), "dpi");
gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(self->builder.get("sbQualityValue")),
GTK_ADJUSTMENT(self->builder.get<GObject>("adjustmentDpi")));
break;
case EXPORT_QUALITY_WIDTH:
case EXPORT_QUALITY_HEIGHT:
gtk_label_set_text(GTK_LABEL(self->builder.get("lbQualityUnit")), "px");
gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(self->builder.get("sbQualityValue")),
GTK_ADJUSTMENT(self->builder.get<GObject>("adjustmentHeightWidth")));
break;
}
}
void ExportDialog::onSuccessCallback(ExportDialog* self) {
self->confirmed = true;
self->progressiveMode = gtk_check_button_get_active(GTK_CHECK_BUTTON(self->builder.get("cbProgressiveMode")));
self->backgroundType = static_cast<ExportBackgroundType>(
gtk_combo_box_get_active(GTK_COMBO_BOX(self->builder.get("cbBackgroundType"))));
self->pageRanges = [self]() {
GtkWidget* rdRangeCurrent = self->builder.get("rdRangeCurrent");
GtkWidget* rdRangePages = self->builder.get("rdRangePages");
if (gtk_check_button_get_active(GTK_CHECK_BUTTON(rdRangePages))) {
return ElementRange::parse(gtk_editable_get_text(GTK_EDITABLE(self->builder.get("txtPages"))),
self->pageCount);
}
if (gtk_check_button_get_active(GTK_CHECK_BUTTON(rdRangeCurrent))) {
PageRangeVector range;
range.emplace_back(self->currentPage - 1, self->currentPage - 1);
return range;
}
PageRangeVector range;
range.emplace_back(0, self->pageCount - 1);
return range;
}();
self->pdfExportBackend = ExportBackend::fromString(
gtk_combo_box_get_active_id(GTK_COMBO_BOX(self->builder.get("cbtPdfExportBackend"))));
self->qualityParameter = RasterImageQualityParameter(
static_cast<ExportQualityCriterion>(
gtk_combo_box_get_active(GTK_COMBO_BOX(self->builder.get("cbQuality")))),
gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(self->builder.get("sbQualityValue"))));
gtk_window_close(self->window.get());
}
auto ExportDialog::getPngQualityParameter() const -> RasterImageQualityParameter { return qualityParameter; }
auto ExportDialog::isConfirmed() const -> bool { return this->confirmed; }
auto ExportDialog::progressiveModeSelected() const -> bool { return this->progressiveMode; }
auto ExportDialog::getBackgroundType() const -> ExportBackgroundType { return backgroundType; }
auto ExportDialog::getRange() const -> const PageRangeVector& { return pageRanges; }
@@ -1,76 +0,0 @@
/*
* Xournal++
*
* Dialog with export settings
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <cstddef> // for size_t
#include <functional>
#include <gtk/gtk.h> // for GtkComboBox, GtkWindow
#include "control/jobs/BaseExportJob.h" // for ExportBackgroundType
#include "control/jobs/ImageExport.h" // for RasterImageQualityParameter
#include "gui/Builder.h"
#include "pdf/base/PdfExportBackend.h"
#include "util/ElementRange.h" // for PageRangeVector
#include "util/raii/GtkWindowUPtr.h"
class GladeSearchpath;
namespace xoj::popup {
class ExportDialog {
public:
ExportDialog(GladeSearchpath* gladeSearchPath, ExportGraphicsFormat format, size_t currentPage, size_t pageCount,
bool hasPdfBackground, std::function<void(const ExportDialog&)> callbackFun);
~ExportDialog();
public:
bool isConfirmed() const;
const PageRangeVector& getRange() const;
bool progressiveModeSelected() const;
ExportBackgroundType getBackgroundType() const;
inline ExportBackend getPdfExportBackend() const { return pdfExportBackend; }
/**
* @brief Reads the quality parameter from the dialog
*
* @return The selected quality parameter
*/
RasterImageQualityParameter getPngQualityParameter() const;
inline GtkWindow* getWindow() const { return window.get(); }
private:
/**
* @brief Handler for changes in combobox cbQuality
*/
static void selectQualityCriterion(GtkComboBox* comboBox, ExportDialog* self);
static void onSuccessCallback(ExportDialog* self);
private:
xoj::util::GtkWindowUPtr window;
size_t currentPage = 0;
size_t pageCount = 0;
bool confirmed = false;
bool progressiveMode;
ExportBackgroundType backgroundType;
ExportBackend pdfExportBackend;
RasterImageQualityParameter qualityParameter;
PageRangeVector pageRanges;
Builder builder;
std::function<void(const ExportDialog&)> callbackFun;
};
}; // namespace xoj::popup
@@ -1,192 +0,0 @@
#include "ExtEdLatexDialog.h"
#include <fstream> // for ofstream
#include <system_error>
#include <gio/gio.h> // for g_subprocess*
#include <glib-object.h> // for g_object_unref, g_clear_object
#include <glib.h>
#include <gtk/gtk.h> // for gtk_widget_set_sensitive
#include "control/Control.h"
#include "control/LatexController.h"
#include "control/settings/LatexSettings.h"
#include "gui/Builder.h"
#include "gui/GladeSearchpath.h"
#include "util/PathUtil.h" // for readString
#include "util/XojMsgBox.h" // for XojMsgBox
#include "util/i18n.h" // for FS, _, _F, N_
#include "util/raii/GLibGuards.h" // for GErrorGuard, GStrvGuard
#include "AbstractLatexDialog.h"
constexpr auto UI_FILE_NAME = "extEdTexDialog.glade";
constexpr auto UI_DIALOG_ID = "extEdTexDialog";
ExtEdLatexDialog::ExtEdLatexDialog(GladeSearchpath* gladeSearchPath, std::unique_ptr<LatexController> ctrl):
AbstractLatexDialog(std::move(ctrl)) {
Builder builder(gladeSearchPath, UI_FILE_NAME);
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_ID)));
populateStandardWidgetsFromBuilder(builder);
btContEdit = GTK_BUTTON(builder.get("btContEdit"));
connectStandardSignals();
tempf = texCtrl->texTmpDir / "edit.";
// Append file extension.
tempf += texCtrl->settings.temporaryFileExt;
// Write initial content
std::ofstream os{tempf};
if (!texCtrl->initialTex.empty()) {
os << texCtrl->initialTex;
} else {
os << texCtrl->settings.defaultText;
}
os.close();
g_signal_connect(getWindow(), "show", G_CALLBACK(+[](GtkWidget*, gpointer d) {
auto self = static_cast<ExtEdLatexDialog*>(d);
if (!self->texCtrl->temporaryRender) {
// Trigger an asynchronous compilation if we are not using a preexisting TexImage
// Keep this after popup.show() so that if an error message is to be displayed (e.g.
// missing Tex executable), it'll appear on top of the LatexDialog.
LatexController::handleTexChanged(self->texCtrl.get());
}
self->openEditor();
}),
this);
g_signal_connect(btContEdit, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) {
auto* self = static_cast<ExtEdLatexDialog*>(d);
self->openEditor();
}),
this);
}
ExtEdLatexDialog::~ExtEdLatexDialog() {
try {
fs::remove(tempf);
} catch (const std::system_error& e) {
g_warning("Failed to remove LaTeX edit file: %s", e.what());
}
if (editorCancellable) {
g_cancellable_cancel(editorCancellable);
g_object_unref(editorCancellable);
}
};
void ExtEdLatexDialog::setCompilationStatus(bool isTexValid, bool isCompilationDone,
const std::string& compilationOutput) {
if (texCtrl->settings.externalEditorAutoConfirm && !editorCancellable && isTexValid && isCompilationDone) {
texCtrl->insertTexImage();
gtk_window_close(getWindow());
return;
}
AbstractLatexDialog::setCompilationStatus(isTexValid, isCompilationDone, compilationOutput);
};
void ExtEdLatexDialog::openEditor() {
if (editorCancellable) {
return;
}
xoj::util::GErrorGuard err{};
xoj::util::GStrvGuard argv{};
std::string editorCmd = texCtrl->settings.externalEditorCmd;
if (editorCmd.empty()) {
auto maybeEditor = g_getenv("EDITOR");
if (!maybeEditor) {
XojMsgBox::showErrorToUser(
getWindow(),
"No external editor configured and no $EDITOR environment variable set. Please configure an "
"external editor command or disable the external editor functionality to use a built-in one.");
return;
}
editorCmd = maybeEditor;
}
editorCmd += " '";
editorCmd += char_cast(tempf.u8string());
editorCmd += "'";
if (!g_shell_parse_argv(editorCmd.c_str(), nullptr, xoj::util::out_ptr(argv), xoj::util::out_ptr(err))) {
XojMsgBox::showErrorToUser(getWindow(), FS(_F("Failed to parse external editor command: {1}") % err->message));
return;
}
xoj::util::GObjectSPtr<GSubprocessLauncher> launcher(g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_NONE),
xoj::util::adopt);
Color textColor = texCtrl->control->getToolHandler()->getTool(TOOL_TEXT).getColor();
std::string colorStr = Util::rgb_to_hex_string(textColor).substr(1);
g_subprocess_launcher_setenv(launcher.get(), "XPP_TEXT_COLOR", colorStr.c_str(), TRUE);
xoj::util::GObjectSPtr<GSubprocess> process(
g_subprocess_launcher_spawnv(launcher.get(), argv.get(), xoj::util::out_ptr(err)), xoj::util::adopt);
if (err) {
XojMsgBox::showErrorToUser(getWindow(), FS(_F("Could not spawn editor: {1}") % err->message));
return;
}
gtk_widget_set_sensitive(GTK_WIDGET(btContEdit), false);
editorCancellable = g_cancellable_new();
g_subprocess_wait_async(process.get(), editorCancellable, reinterpret_cast<GAsyncReadyCallback>(editorWaitCallback),
this);
}
void ExtEdLatexDialog::editorWaitCallback(GObject* processObj, GAsyncResult* res, ExtEdLatexDialog* self) {
auto process = G_SUBPROCESS(processObj);
xoj::util::GErrorGuard err;
if (!g_subprocess_wait_finish(process, res, xoj::util::out_ptr(err))) {
// If we cancelled the operation, that's because the user closed the popup while the editor was still open. We
// must not interact with self now, because that's likely already been destroyed. We kill the editor and then
// return.
if (g_error_matches(err.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
#ifdef G_OS_UNIX
g_subprocess_send_signal(process, SIGTERM);
#else
// The g_subprocess_send_signal API is unavailable on non-unix platforms, so we use the cross-platform
// g_subprocess_force_exit function. We don't use it on supported platforms so we can send a "nicer" exit
// signal if available, as g_subprocess_force_exit sends SIGKILL on Unices.
g_subprocess_force_exit(process);
#endif
return;
}
XojMsgBox::showErrorToUser(self->getWindow(), FS(_F("Could not wait for editor: {1}") % err->message));
return;
}
g_clear_object(&self->editorCancellable);
gtk_widget_set_sensitive(GTK_WIDGET(self->btContEdit), true);
auto status = g_subprocess_get_status(process);
if (status != 0) {
XojMsgBox::showErrorToUser(self->getWindow(), FS(_F("Editor exited with bad status {1}") % status));
return;
}
auto content = self->getBufferContents();
self->texCtrl->triggerImageUpdate(content);
}
std::string ExtEdLatexDialog::getBufferContents() {
auto content = Util::readString(tempf);
if (!content) {
return std::string();
}
// Remove a possible trailing newline
if (!content->empty() && content->back() == '\n') {
content->pop_back();
}
return *content;
}
@@ -1,48 +0,0 @@
/*
* Xournal++
*
* Latex dialog for an external editor
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <glib.h> // for gpointer
#include <gtk/gtk.h> // for GtkWidget
#include "gui/GladeSearchpath.h"
#include "gui/dialog/AbstractLatexDialog.h"
class LatexController;
class ExtEdLatexDialog final: public AbstractLatexDialog {
public:
ExtEdLatexDialog() = delete;
ExtEdLatexDialog(const ExtEdLatexDialog& other) = delete;
ExtEdLatexDialog& operator=(const ExtEdLatexDialog& other) = delete;
ExtEdLatexDialog(GladeSearchpath* gladeSearchPath, std::unique_ptr<LatexController> texCtrl);
~ExtEdLatexDialog() override;
std::string getBufferContents() override;
private:
void setCompilationStatus(bool isTexValid, bool isCompilationDone, const std::string& compilationOutput) override;
/**
* @brief a callback for the editor exiting
*/
static void editorWaitCallback(GObject* processObj, GAsyncResult* res, ExtEdLatexDialog* self);
/**
* @brief Open the editor or do nothing if it's already open.
*/
void openEditor();
private:
GtkButton* btContEdit;
fs::path tempf;
GCancellable* editorCancellable = nullptr;
};
@@ -1,44 +0,0 @@
#include "FileChooserFiltersHelper.h"
#include "util/i18n.h"
namespace xoj {
static void addMimeTypeFilter(GtkFileChooser* fc, const char* name, const char* mime) {
GtkFileFilter* filterPdf = gtk_file_filter_new();
gtk_file_filter_set_name(filterPdf, name);
gtk_file_filter_add_mime_type(filterPdf, mime);
gtk_file_chooser_add_filter(fc, filterPdf);
}
void addFilterAllFiles(GtkFileChooser* fc) {
GtkFileFilter* filterAll = gtk_file_filter_new();
gtk_file_filter_set_name(filterAll, _("All files"));
gtk_file_filter_add_pattern(filterAll, "*");
gtk_file_chooser_add_filter(fc, filterAll);
}
void addFilterSupported(GtkFileChooser* fc) {
GtkFileFilter* filterSupported = gtk_file_filter_new();
gtk_file_filter_set_name(filterSupported, _("Supported files"));
gtk_file_filter_add_mime_type(filterSupported, "application/x-xojpp");
gtk_file_filter_add_mime_type(filterSupported, "application/x-xopp");
gtk_file_filter_add_mime_type(filterSupported, "application/x-xopt");
gtk_file_filter_add_mime_type(filterSupported, "application/pdf");
gtk_file_filter_add_pattern(filterSupported, "*.moj"); // MrWriter
gtk_file_chooser_add_filter(fc, filterSupported);
}
void addFilterPdf(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("PDF files"), "application/pdf"); }
void addFilterXoj(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("Xournal files"), "application/x-xojpp"); }
void addFilterXopp(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("Xournal++ files"), "application/x-xopp"); }
void addFilterXopt(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("Xournal++ template"), "application/x-xopt"); }
void addFilterSvg(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("SVG graphics"), "image/svg+xml"); }
void addFilterPng(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("PNG graphics"), "image/png"); }
void addFilterImages(GtkFileChooser* fc) {
GtkFileFilter* filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, _("Image files"));
gtk_file_filter_add_pixbuf_formats(filter);
gtk_file_chooser_add_filter(fc, filter);
}
}; // namespace xoj
@@ -1,26 +0,0 @@
/*
* Xournal++
*
* Helper functions to add filters to GtkFileChooserDialogs
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <gtk/gtk.h>
namespace xoj {
void addFilterAllFiles(GtkFileChooser* fc);
void addFilterSupported(GtkFileChooser* fc);
void addFilterPdf(GtkFileChooser* fc);
void addFilterXoj(GtkFileChooser* fc);
void addFilterXopp(GtkFileChooser* fc);
void addFilterXopt(GtkFileChooser* fc);
void addFilterSvg(GtkFileChooser* fc);
void addFilterPng(GtkFileChooser* fc);
void addFilterImages(GtkFileChooser* fc); ///< All images supported by GdkPixbuf
}; // namespace xoj
@@ -1,216 +0,0 @@
#include "FormatDialog.h"
#include <string> // for operator==, string, basic_string
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
#include <glib.h> // for g_list_free, GList
#include "control/settings/Settings.h" // for Settings
#include "gui/PaperFormatUtils.h" // for PaperFormatUtils
#include "model/FormatDefinitions.h" // for FormatUnits, XOJ_UNITS, XOJ_U...
#include "util/GListView.h" // for GListView, GListView<>::GList...
#include "util/StringUtils.h" // for StringUtils
#include "util/i18n.h" // for _
class GladeSearchpath;
constexpr auto UI_FILE = "pageFormat.glade";
constexpr auto UI_DIALOG_NAME = "pageFormatDialog";
using namespace xoj::popup;
using xoj::util::GtkPaperSizeUPtr;
FormatDialog::FormatDialog(GladeSearchpath* gladeSearchPath, Settings* settings, double width, double height,
std::function<void(double, double)> callback):
settings(settings),
selectedScale(settings->getSizeUnitIndex()),
scale(XOJ_UNITS[selectedScale].scale),
origWidth(width),
origHeight(height),
callbackFun(callback) {
Builder builder(gladeSearchPath, UI_FILE);
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
paperTemplatesCombo = GTK_COMBO_BOX(builder.get("cbTemplate"));
landscapeButton = GTK_TOGGLE_BUTTON(builder.get("btLandscape"));
portraitButton = GTK_TOGGLE_BUTTON(builder.get("btPortrait"));
widthSpin = GTK_SPIN_BUTTON(builder.get("spinWidth"));
heightSpin = GTK_SPIN_BUTTON(builder.get("spinHeight"));
GtkWidget* cbUnit = builder.get("cbUnit");
for (int i = 0; i < XOJ_UNIT_COUNT; i++) {
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cbUnit), XOJ_UNITS[i].name);
}
gtk_combo_box_set_active(GTK_COMBO_BOX(cbUnit), this->selectedScale);
PaperFormatUtils::loadDefaultPaperSizes(paperSizes);
paperSizes.emplace_back(_("Custom"));
PaperFormatUtils::fillPaperFormatDropDown(paperSizes, paperTemplatesCombo);
paperSizes.pop_back(); // Remove the custom option to prevent it from being matched in the
// spinValueChangedCb function, as its presence would lead to errors
reset(this);
g_signal_connect(landscapeButton, "toggled", G_CALLBACK(landscapeSelectedCb), this);
g_signal_connect(portraitButton, "toggled", G_CALLBACK(portraitSelectedCb), this);
g_signal_connect(paperTemplatesCombo, "changed", G_CALLBACK(cbFormatChangedCb), this);
g_signal_connect(cbUnit, "changed", G_CALLBACK(cbUnitChanged), this);
g_signal_connect_swapped(widthSpin, "value-changed", G_CALLBACK(spinValueChangedCb), this);
g_signal_connect_swapped(heightSpin, "value-changed", G_CALLBACK(spinValueChangedCb), this);
g_signal_connect_swapped(builder.get("btReset"), "clicked", G_CALLBACK(reset), this);
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(+[](FormatDialog* self) {
self->settings->setSizeUnitIndex(self->selectedScale);
self->callbackFun(gtk_spin_button_get_value(self->widthSpin) * self->scale,
gtk_spin_button_get_value(self->heightSpin) * self->scale);
gtk_window_close(self->window.get());
}),
this);
}
void FormatDialog::setOrientation(Orientation orientation) {
if (this->orientation == orientation) {
return;
}
this->orientation = orientation;
gtk_toggle_button_set_active(portraitButton, orientation == ORIENTATION_PORTRAIT);
gtk_toggle_button_set_active(landscapeButton, orientation == ORIENTATION_LANDSCAPE);
}
void FormatDialog::spinValueChangedCb(FormatDialog* dlg) {
if (dlg->ignoreSpinChange) {
return;
}
double width = gtk_spin_button_get_value(dlg->widthSpin) * dlg->scale;
double height = gtk_spin_button_get_value(dlg->heightSpin) * dlg->scale;
if (width < height) {
dlg->setOrientation(ORIENTATION_PORTRAIT);
} else if (width > height) {
dlg->setOrientation(ORIENTATION_LANDSCAPE);
} else {
dlg->setOrientation(ORIENTATION_NOT_DEFINED);
}
int i = 0;
for (auto& size: dlg->paperSizes) {
xoj_assert(std::holds_alternative<GtkPaperSizeUPtr>(size));
double w = gtk_paper_size_get_width(std::get<GtkPaperSizeUPtr>(size).get(), GTK_UNIT_POINTS);
double h = gtk_paper_size_get_height(std::get<GtkPaperSizeUPtr>(size).get(), GTK_UNIT_POINTS);
if ((static_cast<int>(w - width) == 0 && static_cast<int>(h - height) == 0) ||
(static_cast<int>(h - width) == 0 && static_cast<int>(w - height) == 0)) {
break;
}
i++;
}
gtk_combo_box_set_active(dlg->paperTemplatesCombo, i);
}
void FormatDialog::cbUnitChanged(GtkComboBox* widget, FormatDialog* dlg) {
int selectd = gtk_combo_box_get_active(widget);
if (dlg->selectedScale == selectd) {
return;
}
double width = gtk_spin_button_get_value(dlg->widthSpin) * dlg->scale;
double height = gtk_spin_button_get_value(dlg->heightSpin) * dlg->scale;
dlg->selectedScale = selectd;
dlg->scale = XOJ_UNITS[dlg->selectedScale].scale;
dlg->setSpinValues(width / dlg->scale, height / dlg->scale);
}
void FormatDialog::cbFormatChangedCb(GtkComboBox* widget, FormatDialog* dlg) {
GtkTreeIter iter;
if (!gtk_combo_box_get_active_iter(widget, &iter)) {
return;
}
GtkTreeModel* model = gtk_combo_box_get_model(widget);
GValue value = {0};
gtk_tree_model_get_value(model, &iter, 1, &value);
if (!G_VALUE_HOLDS_POINTER(&value)) {
return;
}
auto* s = static_cast<GtkPaperSize*>(g_value_get_pointer(&value));
if (s == nullptr) {
return;
}
double width = gtk_paper_size_get_width(s, GTK_UNIT_POINTS) / dlg->scale;
double height = gtk_paper_size_get_height(s, GTK_UNIT_POINTS) / dlg->scale;
if (dlg->orientation == ORIENTATION_LANDSCAPE) {
if (width < height) {
std::swap(width, height);
}
} else {
if (width > height) {
std::swap(width, height);
}
dlg->setOrientation(ORIENTATION_PORTRAIT);
}
dlg->setSpinValues(width, height);
}
void FormatDialog::portraitSelectedCb(GtkToggleButton* bt, FormatDialog* dlg) {
bool activated = gtk_toggle_button_get_active(bt);
if (activated) {
gtk_toggle_button_set_active(dlg->landscapeButton, false);
dlg->orientation = ORIENTATION_PORTRAIT;
double width = gtk_spin_button_get_value(dlg->widthSpin);
double height = gtk_spin_button_get_value(dlg->heightSpin);
if (width > height) {
// Exchange width and height
dlg->setSpinValues(height, width);
}
}
}
void FormatDialog::landscapeSelectedCb(GtkToggleButton* bt, FormatDialog* dlg) {
bool activated = gtk_toggle_button_get_active(bt);
if (activated) {
gtk_toggle_button_set_active(dlg->portraitButton, false);
dlg->orientation = ORIENTATION_LANDSCAPE;
double width = gtk_spin_button_get_value(dlg->widthSpin);
double height = gtk_spin_button_get_value(dlg->heightSpin);
if (width < height) {
// Exchange width and height
dlg->setSpinValues(height, width);
}
}
}
void FormatDialog::reset(FormatDialog* self) {
self->setSpinValues(self->origWidth / self->scale, self->origHeight / self->scale);
spinValueChangedCb(self);
}
void FormatDialog::setSpinValues(double width, double height) {
ignoreSpinChange = true;
gtk_spin_button_set_value(widthSpin, width);
gtk_spin_button_set_value(heightSpin, height);
ignoreSpinChange = false;
}
@@ -1,72 +0,0 @@
/*
* Xournal++
*
* The page format dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <functional>
#include <memory> // for unique_ptr
#include <vector> // for vector
#include <gtk/gtk.h> // for gtk_paper_size_free, GtkComboBox, GtkToggl...
#include "gui/Builder.h"
#include "gui/PaperFormatUtils.h" // for PaperFormatMenuOptionVector
#include "util/raii/GtkWindowUPtr.h"
class GladeSearchpath;
class Settings;
namespace xoj::popup {
class FormatDialog {
public:
FormatDialog(GladeSearchpath* gladeSearchPath, Settings* settings, double width, double height,
std::function<void(double w, double h)> callback);
inline GtkWindow* getWindow() const { return window.get(); }
private:
enum Orientation { ORIENTATION_NOT_DEFINED, ORIENTATION_LANDSCAPE, ORIENTATION_PORTRAIT };
void setOrientation(Orientation orientation);
void setSpinValues(double width, double heigth);
static void portraitSelectedCb(GtkToggleButton* bt, FormatDialog* dlg);
static void landscapeSelectedCb(GtkToggleButton* bt, FormatDialog* dlg);
static void cbFormatChangedCb(GtkComboBox* widget, FormatDialog* dlg);
static void cbUnitChanged(GtkComboBox* widget, FormatDialog* dlg);
static void spinValueChangedCb(FormatDialog* dlg);
static void reset(FormatDialog* self);
private:
Settings* settings = nullptr;
PaperFormatUtils::PaperFormatMenuOptionVector paperSizes;
Orientation orientation = ORIENTATION_NOT_DEFINED;
int selectedScale = 0;
double scale = 0;
double origWidth = 0;
double origHeight = 0;
bool ignoreSpinChange = false;
GtkComboBox* paperTemplatesCombo;
GtkSpinButton* widthSpin;
GtkSpinButton* heightSpin;
GtkToggleButton* landscapeButton;
GtkToggleButton* portraitButton;
xoj::util::GtkWindowUPtr window;
std::function<void(double w, double h)> callbackFun;
};
}; // namespace xoj::popup
@@ -1,34 +0,0 @@
#include "GotoDialog.h"
#include <memory> // for allocator
#include "gui/Builder.h"
class GladeSearchpath;
constexpr auto UI_FILE = "goto.glade";
constexpr auto UI_DIALOG_NAME = "gotoDialog";
using namespace xoj::popup;
GotoDialog::GotoDialog(GladeSearchpath* gladeSearchPath, size_t initialPage, size_t maxPage,
std::function<void(size_t)> callback):
callback(callback) {
Builder builder(gladeSearchPath, UI_FILE);
this->window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
this->spinButton = GTK_SPIN_BUTTON(builder.get("spinPage"));
gtk_spin_button_set_range(this->spinButton, 1, static_cast<double>(maxPage));
gtk_spin_button_set_value(this->spinButton, static_cast<double>(initialPage + 1));
gtk_editable_select_region(GTK_EDITABLE(this->spinButton), 0, -1); // Select the entire content
auto onSuccessCallback = G_CALLBACK(+[](GotoDialog* self) {
self->callback(static_cast<size_t>(gtk_spin_button_get_value(GTK_SPIN_BUTTON(self->spinButton))));
gtk_window_close(self->window.get());
});
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
g_signal_connect_swapped(builder.get("btOk"), "clicked", onSuccessCallback, this);
}
GotoDialog::~GotoDialog() = default;
@@ -1,38 +0,0 @@
/*
* Xournal++
*
* Goto-Page dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <functional>
#include <gtk/gtk.h>
#include "util/raii/GtkWindowUPtr.h"
class GladeSearchpath;
namespace xoj::popup {
class GotoDialog {
public:
GotoDialog(GladeSearchpath* gladeSearchPath, size_t initialPage, size_t maxPage,
std::function<void(size_t)> callback);
~GotoDialog();
public:
inline GtkWindow* getWindow() const { return window.get(); }
private:
xoj::util::GtkWindowUPtr window;
GtkSpinButton* spinButton = nullptr;
std::function<void(size_t)> callback;
};
}; // namespace xoj::popup
@@ -1,184 +0,0 @@
/*
* Xournal++
*
* Latex implementation
*
* @author W Brenna
* http://wbrenna.ca
*
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#include "IntEdLatexDialog.h"
#include <sstream> // for operator<<, basic_ostream
#include <utility> // for move
#include <glib.h> // for g_free, gpointer, guint
#include <poppler-document.h> // for poppler_document_get_n_p...
#include <poppler-page.h> // for poppler_page_get_size
#include "AbstractLatexDialog.h"
#include "config-features.h"
#ifdef ENABLE_GTK_SOURCEVIEW
#include <gtksourceview/gtksource.h> // for GTK_SOURCE_VIEW, gtk_sou...
#endif
#include "control/LatexController.h"
#include "control/settings/LatexSettings.h" // for LatexSettings
#include "gui/Builder.h"
#include "model/Font.h" // for XojFont
#include "util/StringUtils.h" // for replace_pair, StringUtils
#include "util/raii/CStringWrapper.h"
class GladeSearchpath;
constexpr auto UI_FILE_NAME = "intEdTexDialog.glade";
constexpr auto UI_DIALOG_ID = "intEdTexDialog";
constexpr auto TEX_BOX_WIDGET_NAME = "texBox";
IntEdLatexDialog::IntEdLatexDialog(GladeSearchpath* gladeSearchPath, std::unique_ptr<LatexController> ctrl):
AbstractLatexDialog(std::move(ctrl)), cssProvider(gtk_css_provider_new(), xoj::util::adopt) {
Builder builder(gladeSearchPath, UI_FILE_NAME);
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_ID)));
populateStandardWidgetsFromBuilder(builder);
#ifdef ENABLE_GTK_SOURCEVIEW
this->texBox = gtk_source_view_new();
#else
this->texBox = gtk_text_view_new();
#endif
gtk_widget_set_name(this->texBox, TEX_BOX_WIDGET_NAME);
gtk_text_view_set_editable(GTK_TEXT_VIEW(this->texBox), true);
this->textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(this->texBox));
const auto& settings = texCtrl->settings;
if (texCtrl->initialTex.empty()) {
gtk_text_buffer_set_text(this->textBuffer, settings.defaultText.c_str(), -1);
// Preselect the default
GtkTextIter start, end;
gtk_text_buffer_get_bounds(this->textBuffer, &start, &end);
gtk_text_buffer_select_range(this->textBuffer, &start, &end);
} else {
gtk_text_buffer_set_text(this->textBuffer, texCtrl->initialTex.c_str(), -1);
}
#if GTK_MAJOR_VERSION == 3
// Widgets are visible by default in gtk4
gtk_widget_show_all(GTK_WIDGET(texBox));
#endif
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(builder.get("texBoxContainer")), this->texBox);
#ifdef ENABLE_GTK_SOURCEVIEW
// We own neither the languageManager, the styleSchemeManager, nor the sourceLanguage.
// Do not attempt to free them.
GtkSourceStyleSchemeManager* styleSchemeManager = gtk_source_style_scheme_manager_get_default();
GtkSourceLanguageManager* lm = gtk_source_language_manager_get_default();
// Select the TeX highlighting scheme
GtkSourceLanguage* lang = gtk_source_language_manager_guess_language(lm, "file.tex", nullptr);
std::string styleSchemeId = settings.sourceViewThemeId;
GtkSourceStyleScheme* styleScheme =
gtk_source_style_scheme_manager_get_scheme(styleSchemeManager, styleSchemeId.c_str());
if (settings.sourceViewSyntaxHighlight) {
gtk_source_buffer_set_language(GTK_SOURCE_BUFFER(this->textBuffer), lang);
}
gtk_source_view_set_auto_indent(GTK_SOURCE_VIEW(this->texBox), settings.sourceViewAutoIndent);
gtk_source_view_set_indent_on_tab(GTK_SOURCE_VIEW(this->texBox), settings.sourceViewAutoIndent);
gtk_source_view_set_show_line_numbers(GTK_SOURCE_VIEW(this->texBox), settings.sourceViewShowLineNumbers);
if (styleScheme) {
gtk_source_buffer_set_style_scheme(GTK_SOURCE_BUFFER(this->textBuffer), styleScheme);
}
#endif
// Enable/disable word-wrap.
GtkWrapMode texBoxWrapMode = GTK_WRAP_NONE;
if (settings.editorWordWrap) {
texBoxWrapMode = GTK_WRAP_WORD_CHAR;
}
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(this->texBox), texBoxWrapMode);
std::stringstream texBoxCssBuilder;
if (settings.useCustomEditorFont) {
std::string fontName = settings.editorFont.getName();
// Escape "'" and "\" characters in the fontName
StringUtils::replaceAllChars(fontName, {replace_pair('\\', "\\\\")});
StringUtils::replaceAllChars(fontName, {replace_pair('\'', "\\'")});
texBoxCssBuilder << "#" << TEX_BOX_WIDGET_NAME << " {";
texBoxCssBuilder << " font-size: " << settings.editorFont.getSize() << "pt;";
texBoxCssBuilder << " font-family: '" << fontName << "';";
texBoxCssBuilder << " } ";
gtk_css_provider_load_from_data(this->cssProvider.get(), texBoxCssBuilder.str().c_str(), -1, nullptr);
// Apply the CSS to both the texBox and the drawing area.
gtk_style_context_add_provider(gtk_widget_get_style_context(GTK_WIDGET(this->previewDrawingArea)),
GTK_STYLE_PROVIDER(this->cssProvider.get()),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
gtk_style_context_add_provider(gtk_widget_get_style_context(GTK_WIDGET(this->texBox)),
GTK_STYLE_PROVIDER(this->cssProvider.get()),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
connectStandardSignals();
/*
* Connect handleTexChanged() to the text buffer containing the latex code, to trigger rebuilds upon code changes.
*/
g_signal_connect(this->getTextBuffer(), "changed", G_CALLBACK(+[](GtkTextBuffer*, gpointer ctrl) {
LatexController::handleTexChanged(static_cast<LatexController*>(ctrl));
}),
texCtrl.get());
g_signal_connect(this->getWindow(), "delete-event", G_CALLBACK(+[](GtkWidget*, GdkEvent*, gpointer d) -> gboolean {
auto self = static_cast<IntEdLatexDialog*>(d);
/**
* dlg->getTextBuffer() may survive the dialog. When copying all of its content, the
* clipboard owns a ref to the GtkTextBuffer. We must disconnect the signal to avoid
* dereferencing `self` after its destruction
*/
g_signal_handlers_disconnect_by_data(self->getTextBuffer(), self->texCtrl.get());
return false; // Let the callback from PopupWindowWrapper delete the dialog
}),
this);
g_signal_connect(this->getWindow(), "show", G_CALLBACK(+[](GtkWidget*, gpointer d) {
auto texCtrl = static_cast<LatexController*>(d);
if (!texCtrl->temporaryRender) {
// Trigger an asynchronous compilation if we are not using a preexisting TexImage
// Keep this after popup.show() so that if an error message is to be displayed (e.g.
// missing Tex executable), it'll appear on top of the LatexDialog.
LatexController::handleTexChanged(texCtrl);
}
}),
texCtrl.get());
}
IntEdLatexDialog::~IntEdLatexDialog() = default;
auto IntEdLatexDialog::getTextBuffer() -> GtkTextBuffer* { return this->textBuffer; }
auto IntEdLatexDialog::getBufferContents() -> std::string {
GtkTextIter start, end;
gtk_text_buffer_get_bounds(this->textBuffer, &start, &end);
auto content =
xoj::util::OwnedCString::assumeOwnership(gtk_text_buffer_get_text(this->textBuffer, &start, &end, false));
return content.get();
}
@@ -1,55 +0,0 @@
/*
* Xournal++
*
* Latex dialog for the internal editor
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <string> // for string
#include <cairo.h> // for cairo_t
#include <gtk/gtk.h> // for GtkWidget, GtkTextBuffer, GtkWindow
#include <gtk/gtkcssprovider.h> // for GtkCssProvider
#include <poppler.h> // for PopplerDocument, PopplerPage
#include "util/raii/GObjectSPtr.h"
#include "AbstractLatexDialog.h"
class LatexController;
class LatexSettings;
class GladeSearchpath;
class IntEdLatexDialog final: public AbstractLatexDialog {
public:
IntEdLatexDialog() = delete;
IntEdLatexDialog(const IntEdLatexDialog& other) = delete;
IntEdLatexDialog& operator=(const IntEdLatexDialog& other) = delete;
IntEdLatexDialog(GladeSearchpath* gladeSearchPath, std::unique_ptr<LatexController> texCtrl);
virtual ~IntEdLatexDialog();
public:
// Necessary for the controller in order to connect the 'text-changed'
// signal handler
GtkTextBuffer* getTextBuffer();
std::string getBufferContents() override;
private:
// Text field
xoj::util::GObjectSPtr<GtkCssProvider> cssProvider; ///< For tex code display font and size
GtkWidget* texBox;
GtkTextBuffer* textBuffer;
/**
* The final LaTeX string to save once the dialog is closed.
*/
std::string finalLatex;
std::function<void()> callback;
};
@@ -1,88 +0,0 @@
#include "LanguageConfigGui.h"
#include <algorithm> // for find, lower_bound, sort
#include <cstdlib> // for setenv
#include <memory> // for allocator, __alloc_traits<>::...
#include <glib-object.h> // for G_TYPE_STRING
#include <glib.h> // for g_warning, gint
#include "control/settings/Settings.h" // for Settings
#include "util/PathUtil.h" // for getGettextFilepath, getLocale...
#include "util/StringUtils.h" // for StringUtils
#include "util/XojMsgBox.h" // for XojMsgBox
#include "util/i18n.h" // for _
#include "util/safe_casts.h" // for as_unsigned
#include "config.h" // for GETTEXT_PACKAGE
#include "filesystem.h" // for directory_iterator, operator/
LanguageConfigGui::LanguageConfigGui(GtkBox* parent, Settings* settings):
comboBox(gtk_combo_box_text_new(), xoj::util::adopt), settings(settings) {
gtk_box_append(parent, comboBox.get());
#if GTK_MAJOR_VERSION == 3
gtk_widget_show_all(comboBox.get());
#endif
// Fetch available locales
try {
fs::path baseLocaleDir = Util::getGettextFilepath(Util::getLocalePath());
for (auto const& d: fs::directory_iterator(baseLocaleDir)) {
if (auto mofile = (d.path() / "LC_MESSAGES" / GETTEXT_PACKAGE) += ".mo"; fs::exists(mofile)) {
auto u8path = d.path().filename().u8string();
auto casted = char_cast(u8path);
availableLocales.emplace_back(casted.begin(), casted.end());
}
}
} catch (const fs::filesystem_error& e) {
g_warning("Failed to fetch available locales:\n%s", e.what());
}
std::sort(availableLocales.begin(), availableLocales.end());
// No pot file for English
if (auto enPos = std::lower_bound(availableLocales.begin(), availableLocales.end(), "en");
enPos != availableLocales.end() && !StringUtils::startsWith(*enPos, "en")) {
availableLocales.insert(enPos, "en");
}
// Default
availableLocales.insert(availableLocales.begin(), _("System Default"));
auto gtkAvailableLocales = gtk_list_store_new(1, G_TYPE_STRING);
for (auto const& i: availableLocales) {
GtkTreeIter iter;
gtk_list_store_append(gtkAvailableLocales, &iter);
gtk_list_store_set(gtkAvailableLocales, &iter, 0, i.c_str(), -1);
}
gtk_combo_box_set_model(GTK_COMBO_BOX(comboBox.get()), GTK_TREE_MODEL(gtkAvailableLocales));
// Set the current locale if previously selected
auto prefPos = [&]() {
if (auto preferred = settings->getPreferredLocale(); !preferred.empty()) {
if (auto &&endi = availableLocales.end(), prefPos = std::find(availableLocales.begin(), endi, preferred);
prefPos != endi) {
return prefPos;
}
XojMsgBox::showErrorToUser(nullptr, _("Previously selected language not available anymore!"));
}
// Use system default
return availableLocales.begin();
}();
gtk_combo_box_set_active(GTK_COMBO_BOX(comboBox.get()),
static_cast<gint>(std::distance(availableLocales.begin(), prefPos)));
}
void LanguageConfigGui::saveSettings() {
gint pos = gtk_combo_box_get_active(GTK_COMBO_BOX(comboBox.get()));
auto pref = (pos == 0) ? "" : availableLocales[as_unsigned(pos)];
settings->setPreferredLocale(pref);
settings->customSettingsChanged();
#ifdef _WIN32
_putenv_s("LANGUAGE", this->settings->getPreferredLocale().c_str());
#else
setenv("LANGUAGE", this->settings->getPreferredLocale().c_str(), 1);
#endif
}
@@ -1,37 +0,0 @@
/*
* Xournal++
*
* The configuration for a language in the Settings Dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <string> // for string, basic_string
#include <vector> // for vector
#include <gtk/gtk.h> // for GtkWidget, GtkBox
#include "util/raii/GObjectSPtr.h"
class Settings;
class GladeSearchpath;
class LanguageConfigGui {
public:
LanguageConfigGui(GtkBox* parent, Settings* settings);
public:
void saveSettings();
private:
std::vector<std::string> availableLocales;
xoj::util::WidgetSPtr comboBox;
Settings* settings;
};
@@ -1,205 +0,0 @@
#include "LatexSettingsPanel.h"
#include <fstream> // for ifstream, basic_istream
#include <iterator> // for istreambuf_iterator, ope...
#include <string> // for allocator, string
#include <variant> // for get_if
#include <gio/gio.h> // for GSubprocess, g_subproces...
#include <glib-object.h> // for g_object_unref, g_object...
#include <glib.h> // for g_error_free, GError
#include "config-features.h" // for ENABLE_GTK_SOURCEVIEW
#ifdef ENABLE_GTK_SOURCEVIEW
#include <gtksourceview/gtksource.h> // for gtk_source_style_scheme_...
#endif
#include "control/latex/LatexGenerator.h" // for LatexGenerator::GenError
#include "control/settings/LatexSettings.h" // for LatexSettings
#include "gui/Builder.h"
#include "model/Font.h" // for XojFont
#include "util/Color.h" // for Color
#include "util/PathUtil.h" // for fromGFile, getTmpDir...
#include "util/PlaceholderString.h" // for PlaceholderString
#include "util/XojMsgBox.h"
#include "util/gtk4_helper.h"
#include "util/i18n.h" // for FS, _F, _
#include "util/raii/CStringWrapper.h"
#include "filesystem.h" // for path, is_regular_file
class GladeSearchpath;
constexpr auto UI_FILE = "latexSettings.glade";
constexpr auto UI_PANEL_NAME = "latexSettingsPanel";
LatexSettingsPanel::LatexSettingsPanel(GladeSearchpath* gladeSearchPath):
builder(gladeSearchPath, UI_FILE),
panel(GTK_SCROLLED_WINDOW(builder.get(UI_PANEL_NAME))),
cbAutoDepCheck(GTK_CHECK_BUTTON(builder.get("latexSettingsRunCheck"))),
// Todo(gtk4): replace this GtkFileChooserButton (by what?)
globalTemplateChooser(GTK_FILE_CHOOSER(builder.get("latexSettingsTemplateFile"))),
cbUseSystemFont(GTK_CHECK_BUTTON(builder.get("cbUseSystemFont"))) {
g_signal_connect_swapped(builder.get("latexSettingsTestBtn"), "clicked",
G_CALLBACK(+[](LatexSettingsPanel* self) { self->checkDeps(); }), this);
g_signal_connect_swapped(this->cbUseSystemFont, "toggled",
G_CALLBACK(+[](LatexSettingsPanel* self) { self->updateWidgetSensitivity(); }), this);
g_signal_connect_swapped(builder.get("cbUseExternalEditor"), "toggled",
G_CALLBACK(+[](LatexSettingsPanel* self) { self->updateWidgetSensitivity(); }), this);
#ifdef ENABLE_GTK_SOURCEVIEW
GtkBox* themeSelectionBox = GTK_BOX(builder.get("bxThemeSelectionContainer"));
this->sourceViewThemeSelector = gtk_source_style_scheme_chooser_button_new();
gtk_box_append(themeSelectionBox, sourceViewThemeSelector);
gtk_label_set_text(GTK_LABEL(builder.get("lbSourceviewSettingsDescription")), _("LaTeX editor theme:"));
#if GTK_MAJOR_VERSION == 3
// Widgets are visible by default in gtk4
gtk_widget_show(sourceViewThemeSelector);
#endif
#else
this->sourceViewThemeSelector = nullptr;
gtk_label_set_text(GTK_LABEL(builder.get("lbSourceviewSettingsDescription")),
_("GtkSourceView was disabled when building Xournal++! "
"Some options will not be available."));
#endif
}
void LatexSettingsPanel::load(const LatexSettings& settings) {
gtk_check_button_set_active(this->cbAutoDepCheck, settings.autoCheckDependencies);
gtk_editable_set_text(GTK_EDITABLE(builder.get("latexDefaultEntry")), settings.defaultText.c_str());
if (!settings.globalTemplatePath.empty()) {
gtk_file_chooser_set_file(this->globalTemplateChooser, Util::toGFile(settings.globalTemplatePath).get(),
nullptr);
}
gtk_editable_set_text(GTK_EDITABLE(builder.get("latexSettingsGenCmd")), settings.genCmd.c_str());
#ifdef ENABLE_GTK_SOURCEVIEW
std::string themeId = settings.sourceViewThemeId;
GtkSourceStyleSchemeManager* themeManager = gtk_source_style_scheme_manager_get_default();
GtkSourceStyleScheme* theme = gtk_source_style_scheme_manager_get_scheme(themeManager, themeId.c_str());
if (theme) {
gtk_source_style_scheme_chooser_set_style_scheme(GTK_SOURCE_STYLE_SCHEME_CHOOSER(this->sourceViewThemeSelector),
theme);
}
#endif
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbShowLineNumbers")), settings.sourceViewShowLineNumbers);
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbAutoIndent")), settings.sourceViewAutoIndent);
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbSyntaxHighlight")), settings.sourceViewSyntaxHighlight);
// Editor font
std::string editorFontDescription{settings.editorFont.asString()};
gtk_font_chooser_set_font(GTK_FONT_CHOOSER(builder.get("selBtnEditorFont")), editorFontDescription.c_str());
// Should we use the custom editor font?
gtk_check_button_set_active(this->cbUseSystemFont, !settings.useCustomEditorFont);
// Editor word wrap.
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbWordWrap")), settings.editorWordWrap);
// External editor
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbUseExternalEditor")), settings.useExternalEditor);
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbExternalEditorAutoConfirm")),
settings.externalEditorAutoConfirm);
gtk_editable_set_text(GTK_EDITABLE(builder.get("latexExternalEditorCmd")), settings.externalEditorCmd.c_str());
gtk_editable_set_text(GTK_EDITABLE(builder.get("latexTemporaryFileExt")), settings.temporaryFileExt.c_str());
this->updateWidgetSensitivity();
}
void LatexSettingsPanel::save(LatexSettings& settings) {
settings.autoCheckDependencies = gtk_check_button_get_active(this->cbAutoDepCheck);
settings.defaultText = gtk_editable_get_text(GTK_EDITABLE(builder.get("latexDefaultEntry")));
settings.globalTemplatePath = Util::fromGFile(
xoj::util::GObjectSPtr<GFile>(gtk_file_chooser_get_file(this->globalTemplateChooser), xoj::util::adopt)
.get());
settings.genCmd = gtk_editable_get_text(GTK_EDITABLE(builder.get("latexSettingsGenCmd")));
#ifdef ENABLE_GTK_SOURCEVIEW
GtkSourceStyleScheme* theme = gtk_source_style_scheme_chooser_get_style_scheme(
GTK_SOURCE_STYLE_SCHEME_CHOOSER(this->sourceViewThemeSelector));
settings.sourceViewThemeId = gtk_source_style_scheme_get_id(theme);
#endif
settings.sourceViewShowLineNumbers =
gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbShowLineNumbers")));
settings.sourceViewAutoIndent = gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbAutoIndent")));
settings.sourceViewSyntaxHighlight =
gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbSyntaxHighlight")));
GtkFontChooser* fontSelector = GTK_FONT_CHOOSER(builder.get("selBtnEditorFont"));
settings.editorFont = xoj::util::OwnedCString::assumeOwnership(gtk_font_chooser_get_font(fontSelector)).get();
settings.useCustomEditorFont = !gtk_check_button_get_active(this->cbUseSystemFont);
settings.editorWordWrap = gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbWordWrap")));
settings.useExternalEditor = gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbUseExternalEditor")));
settings.externalEditorAutoConfirm =
gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbExternalEditorAutoConfirm")));
settings.externalEditorCmd = gtk_editable_get_text(GTK_EDITABLE(builder.get("latexExternalEditorCmd")));
settings.temporaryFileExt = gtk_editable_get_text(GTK_EDITABLE(builder.get("latexTemporaryFileExt")));
}
void LatexSettingsPanel::checkDeps() {
LatexSettings settings;
this->save(settings);
std::string msg;
bool fail = false;
if (fs::is_regular_file(settings.globalTemplatePath)) {
// Assume the file is encoded as UTF-8 (open in binary mode to avoid surprises)
std::ifstream is(settings.globalTemplatePath, std::ios_base::binary);
if (!is.is_open()) {
msg = FS(_F("Unable to open global template file at {1}. Does it exist?") %
settings.globalTemplatePath.u8string());
fail = true;
} else {
std::string templ(std::istreambuf_iterator<char>(is), {});
std::string sample = LatexGenerator::templateSub(settings.defaultText, templ, Colors::black);
auto const& tmpDir = Util::getTmpDirSubfolder("tex");
auto result = LatexGenerator(settings).asyncRun(tmpDir, sample);
if (auto* proc = std::get_if<GSubprocess*>(&result)) {
GError* err = nullptr;
if (g_subprocess_wait_check(*proc, nullptr, &err)) {
msg = _("Sample LaTeX file generated successfully.");
} else {
msg = FS(_F("Error: {1}. Please check the contents of {2}") % err->message % tmpDir.u8string());
g_error_free(err);
fail = true;
}
g_object_unref(*proc);
} else if (auto* err = std::get_if<LatexGenerator::GenError>(&result)) {
msg = err->message;
fail = true;
}
}
} else {
msg = FS(_F("Error: {1} is not a regular file. Please check your LaTeX template file settings. ") %
settings.globalTemplatePath.u8string());
fail = true;
}
GtkWindow* win = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(panel)));
XojMsgBox::showMessageToUser(win, msg, fail ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO);
}
void LatexSettingsPanel::updateWidgetSensitivity() {
bool useSystemFont = gtk_check_button_get_active(this->cbUseSystemFont);
// Only select a custom font if we're not using the system's.
gtk_widget_set_sensitive(builder.get("boxCustomFontOptions"), !useSystemFont);
#ifndef ENABLE_GTK_SOURCEVIEW
gtk_widget_set_sensitive(builder.get("bxGtkSourceviewSettings"), false);
#endif
auto useExternalEditor = gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbUseExternalEditor")));
gtk_widget_set_sensitive(builder.get("cbExternalEditorAutoConfirm"), useExternalEditor);
gtk_widget_set_sensitive(builder.get("latexExternalEditorCmd"), useExternalEditor);
gtk_widget_set_sensitive(builder.get("latexTemporaryFileExt"), useExternalEditor);
}
@@ -1,50 +0,0 @@
/*
* Xournal++
*
* Latex settings panel
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <gtk/gtk.h> // for GtkToggleButton, GtkFileChooser, GtkWidget
#include "gui/Builder.h"
class GladeSearchpath;
class LatexSettings;
class LatexSettingsPanel {
public:
explicit LatexSettingsPanel(GladeSearchpath*);
LatexSettingsPanel(const LatexSettingsPanel&) = delete;
LatexSettingsPanel& operator=(const LatexSettingsPanel&) = delete;
LatexSettingsPanel(const LatexSettingsPanel&&) = delete;
LatexSettingsPanel& operator=(const LatexSettingsPanel&&) = delete;
void load(const LatexSettings& settings);
void save(LatexSettings& settings);
inline GtkWidget* getPanel() const { return GTK_WIDGET(panel); }
private:
void checkDeps();
/**
* @brief Update whether options accept input ("grayed-out" or not)
* based on the current panel state.
*/
void updateWidgetSensitivity();
Builder builder;
GtkScrolledWindow* panel;
GtkCheckButton* cbAutoDepCheck;
GtkFileChooser* globalTemplateChooser;
GtkWidget* sourceViewThemeSelector;
GtkCheckButton* cbUseSystemFont;
};
@@ -1,224 +0,0 @@
#include "PageTemplateDialog.h"
#include <cstdio> // for sprintf
#include <ctime> // for localtime, strftime, time
#include <fstream> // for ofstream, basic_ostream
#include <memory> // for allocator, unique_ptr
#include <optional> // for optional
#include <string> // for string, operator<<
#include <gdk/gdk.h> // for GdkRGBA
#include <glib-object.h> // for G_CALLBACK, g_signal_c...
#include "control/pagetype/PageTypeHandler.h" // for PageTypeInfo, PageType...
#include "control/settings/Settings.h" // for Settings
#include "gui/Builder.h" // for Builder
#include "gui/dialog/XojOpenDlg.h" // for XojOpenDlg
#include "gui/menus/popoverMenus/PageTypeSelectionPopoverGridOnly.h"
#include "gui/toolbarMenubar/ToolMenuHandler.h"
#include "model/FormatDefinitions.h" // for FormatUnits, XOJ_UNITS
#include "model/PageType.h" // for PageType
#include "util/Color.h" // for GdkRGBA_to_argb, rgb_t...
#include "util/PathUtil.h" // for fromGFile, readString
#include "util/PopupWindowWrapper.h" // for PopupWindowWrapper
#include "util/XojMsgBox.h" // for XojMsgBox
#include "util/i18n.h" // for _
#include "util/serdesstream.h" // for serdes_stream
#include "FormatDialog.h" // for FormatDialog
#include "filesystem.h" // for path
class GladeSearchpath;
constexpr auto UI_FILE = "pageTemplate.glade";
constexpr auto UI_DIALOG_NAME = "templateDialog";
using namespace xoj::popup;
PageTemplateDialog::PageTemplateDialog(GladeSearchpath* gladeSearchPath, Settings* settings, ToolMenuHandler* toolmenu,
PageTypeHandler* types):
gladeSearchPath(gladeSearchPath), settings(settings), toolMenuHandler(toolmenu), types(types) {
model.parse(settings->getPageTemplate());
Builder builder(gladeSearchPath, UI_FILE);
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
// Needs to be initialized after this->window
pageTypeSelectionMenu = std::make_unique<PageTypeSelectionPopoverGridOnly>(types, settings, this);
gtk_menu_button_set_popup(GTK_MENU_BUTTON(builder.get("btBackgroundDropdown")),
pageTypeSelectionMenu->getPopover());
pageSizeLabel = GTK_LABEL(builder.get("lbPageSize"));
backgroundTypeLabel = GTK_LABEL(builder.get("lbBackgroundType"));
backgroundColorChooser = GTK_COLOR_CHOOSER(builder.get("cbBackgroundButton"));
copyLastPageButton = GTK_TOGGLE_BUTTON(builder.get("cbCopyLastPage"));
copyLastPageSizeButton = GTK_TOGGLE_BUTTON(builder.get("cbCopyLastPageSize"));
g_signal_connect_swapped(builder.get("btChangePaperSize"), "clicked",
G_CALLBACK(+[](PageTemplateDialog* self) { self->showPageSizeDialog(); }), this);
g_signal_connect_swapped(builder.get("btLoad"), "clicked",
G_CALLBACK(+[](PageTemplateDialog* self) { self->loadFromFile(); }), this);
g_signal_connect_swapped(builder.get("btSave"), "clicked",
G_CALLBACK(+[](PageTemplateDialog* self) { self->saveToFile(); }), this);
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->getWindow());
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(+[](PageTemplateDialog* self) {
self->saveToModel();
self->settings->setPageTemplate(self->model.toString());
self->toolMenuHandler->setDefaultNewPageType(self->model.getPageInsertType());
self->toolMenuHandler->setDefaultNewPaperSize(
self->model.isCopyLastPageSize() ? std::nullopt :
std::optional(PaperSize(self->model)));
gtk_window_close(self->getWindow());
}),
this);
updateDataFromModel();
}
PageTemplateDialog::~PageTemplateDialog() = default;
void PageTemplateDialog::updateDataFromModel() {
GdkRGBA color = Util::rgb_to_GdkRGBA(model.getBackgroundColor());
gtk_color_chooser_set_rgba(backgroundColorChooser, &color);
updatePageSize();
pageTypeSelectionMenu->setSelectedPT(model.getBackgroundType());
changeCurrentPageBackground(types->getInfoOn(model.getBackgroundType()));
gtk_toggle_button_set_active(copyLastPageButton, model.isCopyLastPageSettings());
gtk_toggle_button_set_active(copyLastPageSizeButton, model.isCopyLastPageSize());
}
void PageTemplateDialog::changeCurrentPageBackground(const PageTypeInfo* info) {
model.setBackgroundType(info->page);
gtk_label_set_text(backgroundTypeLabel, info->name.c_str());
}
void PageTemplateDialog::saveToModel() {
model.setCopyLastPageSettings(gtk_toggle_button_get_active(copyLastPageButton));
model.setCopyLastPageSize(gtk_toggle_button_get_active(copyLastPageSizeButton));
GdkRGBA color;
gtk_color_chooser_get_rgba(backgroundColorChooser, &color);
model.setBackgroundColor(Util::GdkRGBA_to_argb(color));
}
void PageTemplateDialog::saveToFile() {
saveToModel();
GtkWidget* dialog =
gtk_file_chooser_dialog_new(_("Save File"), this->getWindow(), GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"),
GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_OK, nullptr);
GtkFileFilter* filterXoj = gtk_file_filter_new();
gtk_file_filter_set_name(filterXoj, _("Xournal++ template"));
gtk_file_filter_add_mime_type(filterXoj, "application/x-xopt");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filterXoj);
if (!settings->getLastSavePath().empty()) {
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), Util::toGFile(settings->getLastSavePath()).get(),
nullptr);
}
time_t curtime = time(nullptr);
char stime[128];
strftime(stime, sizeof(stime), "%F-Template-%H-%M.xopt", localtime(&curtime));
fs::path saveFilename = stime; // There may be an issue here, if the C and C++ locales do not use the same encoding
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), Util::toGFilename(saveFilename).c_str());
class FileDlg final {
public:
FileDlg(GtkDialog* dialog, PageTemplateDialog* parent): window(GTK_WINDOW(dialog)), parent(parent) {
this->signalId = g_signal_connect(
dialog, "response", G_CALLBACK((+[](GtkDialog* dialog, int response, gpointer data) {
FileDlg* self = static_cast<FileDlg*>(data);
if (response == GTK_RESPONSE_OK) {
auto file = Util::fromGFile(
xoj::util::GObjectSPtr<GFile>(gtk_file_chooser_get_file(GTK_FILE_CHOOSER(dialog)),
xoj::util::adopt)
.get());
auto saveTemplate = [self, dialog](const fs::path& file) {
// Closing the window causes another "response" signal, which we want to ignore
g_signal_handler_disconnect(dialog, self->signalId);
gtk_window_close(GTK_WINDOW(dialog));
self->parent->settings->setLastSavePath(file.parent_path());
auto out = serdes_stream<std::ofstream>(file);
out << self->parent->model.toString();
};
XojMsgBox::replaceFileQuestion(GTK_WINDOW(dialog), std::move(file),
std::move(saveTemplate));
} else {
// Closing the window causes another "response" signal, which we want to ignore
g_signal_handler_disconnect(dialog, self->signalId);
gtk_window_close(GTK_WINDOW(dialog)); // Deletes self, don't do anything after this
}
})),
this);
}
~FileDlg() = default;
inline GtkWindow* getWindow() const { return window.get(); }
private:
xoj::util::GtkWindowUPtr window;
PageTemplateDialog* parent;
gulong signalId;
};
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(GTK_DIALOG(dialog), this);
popup.show(GTK_WINDOW(this->getWindow()));
}
void PageTemplateDialog::loadFromFile() {
xoj::OpenDlg::showOpenTemplateDialog(this->getWindow(), settings, [this](fs::path path) {
auto contents = Util::readString(path);
if (!contents.has_value()) {
return;
}
model.parse(*contents);
updateDataFromModel();
});
}
void PageTemplateDialog::updatePageSize() {
const FormatUnits* formatUnit = &XOJ_UNITS[settings->getSizeUnitIndex()];
char buffer[64];
sprintf(buffer, "%0.2lf", model.getPageWidth() / formatUnit->scale);
std::string pageSize = buffer;
pageSize += formatUnit->name;
pageSize += " x ";
sprintf(buffer, "%0.2lf", model.getPageHeight() / formatUnit->scale);
pageSize += buffer;
pageSize += formatUnit->name;
gtk_label_set_text(pageSizeLabel, pageSize.c_str());
}
void PageTemplateDialog::showPageSizeDialog() {
auto popup = xoj::popup::PopupWindowWrapper<xoj::popup::FormatDialog>(gladeSearchPath, settings,
model.getPageWidth(), model.getPageHeight(),
[dlg = this](double width, double height) {
dlg->model.setPageWidth(width);
dlg->model.setPageHeight(height);
dlg->updatePageSize();
});
popup.show(this->getWindow());
}
/**
* The dialog was confirmed / saved
*/
auto PageTemplateDialog::isSaved() const -> bool { return saved; }
@@ -1,80 +0,0 @@
/*
* Xournal++
*
* Dialog to configure page template for new pages
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <memory> // for unique_ptr
#include "control/settings/PageTemplateSettings.h" // for PageTemplateSettings
#include "util/raii/GtkWindowUPtr.h"
class PageTypeHandler;
class PageTypeInfo;
class GladeSearchpath;
class Settings;
class ToolMenuHandler;
class PageTypeSelectionPopoverGridOnly;
namespace xoj::popup {
class PageTemplateDialog final {
public:
PageTemplateDialog(GladeSearchpath* gladeSearchPath, Settings* settings, ToolMenuHandler* toolmenu,
PageTypeHandler* types);
PageTemplateDialog(PageTemplateDialog&) = delete;
PageTemplateDialog(PageTemplateDialog&&) = delete;
PageTemplateDialog& operator=(PageTemplateDialog&) = delete;
PageTemplateDialog&& operator=(PageTemplateDialog&&) = delete;
~PageTemplateDialog();
public:
/**
* The dialog was confirmed / saved
*/
bool isSaved() const;
void changeCurrentPageBackground(const PageTypeInfo* info);
inline GtkWindow* getWindow() const { return window.get(); }
private:
void showPageSizeDialog();
void updatePageSize();
void saveToFile();
void loadFromFile();
void updateDataFromModel();
void saveToModel();
private:
GladeSearchpath* gladeSearchPath; // For opening subdialogs as needed
xoj::util::GtkWindowUPtr window;
Settings* settings;
ToolMenuHandler* toolMenuHandler;
PageTypeHandler* types;
PageTemplateSettings model;
std::unique_ptr<PageTypeSelectionPopoverGridOnly> pageTypeSelectionMenu;
/**
* The dialog was confirmed / saved
*/
bool saved = false;
private:
// Sub-widgets, owned by this->window
GtkLabel* pageSizeLabel;
GtkLabel* backgroundTypeLabel;
GtkColorChooser* backgroundColorChooser;
GtkToggleButton* copyLastPageButton;
GtkToggleButton* copyLastPageSizeButton;
};
}; // namespace xoj::popup
@@ -1,48 +0,0 @@
#include "PluginDialog.h"
#ifdef ENABLE_PLUGINS
#include <algorithm>
#include <string> // for string
#include "control/settings/Settings.h" // for Settings
#include "gui/Builder.h"
#include "gui/dialog/PluginDialogEntry.h" // for PluginDialogEntry
#include "plugin/PluginController.h" // for PluginController
class GladeSearchpath;
class Plugin;
constexpr auto UI_FILE = "plugin.glade";
constexpr auto UI_DIALOG_NAME = "pluginDialog";
PluginDialog::PluginDialog(GladeSearchpath* gladeSearchPath, Settings* settings,
const std::vector<std::unique_ptr<Plugin>>& pluginList):
settings(settings) {
Builder builder(gladeSearchPath, UI_FILE);
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
GtkBox* pluginBox = GTK_BOX(builder.get("pluginBox"));
std::transform(pluginList.begin(), pluginList.end(), std::back_inserter(this->plugins),
[&](auto& p) { return std::make_unique<PluginDialogEntry>(p.get(), gladeSearchPath, pluginBox); });
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(+[](PluginDialog* self) {
self->saveSettings();
gtk_window_close(self->window.get());
}),
this);
}
void PluginDialog::saveSettings() {
std::string pluginEnabled;
std::string pluginDisabled;
// Save plugin settings
for (auto&& bcg: this->plugins) { bcg->saveSettings(pluginEnabled, pluginDisabled); }
settings->setPluginEnabled(pluginEnabled);
settings->setPluginDisabled(pluginDisabled);
}
#endif
@@ -1,48 +0,0 @@
/*
* Xournal++
*
* Plugin manage dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include "config-features.h"
#ifdef ENABLE_PLUGINS
#include <memory> // for unique_ptr
#include <vector> // for vector
#include "util/raii/GtkWindowUPtr.h"
#include "PluginDialogEntry.h" // for PluginDialogEntry
class Plugin;
class PluginController;
class Settings;
class GladeSearchpath;
class PluginDialog {
public:
PluginDialog(GladeSearchpath* gladeSearchPath, Settings* settings,
const std::vector<std::unique_ptr<Plugin>>& plugins);
public:
inline GtkWindow* getWindow() const { return window.get(); }
private:
void saveSettings();
private:
Settings* settings;
std::vector<std::unique_ptr<PluginDialogEntry>> plugins;
xoj::util::GtkWindowUPtr window;
};
#endif
@@ -1,52 +0,0 @@
#include "PluginDialogEntry.h"
#ifdef ENABLE_PLUGINS
#include "gui/Builder.h"
#include "plugin/Plugin.h"
#include "util/gtk4_helper.h"
#include "util/i18n.h"
#include "filesystem.h" // for path
class GladeSearchpath;
constexpr auto UI_FILE = "pluginEntry.glade";
constexpr auto UI_WIDGET_NAME = "pluginMainBox";
PluginDialogEntry::PluginDialogEntry(Plugin* plugin, GladeSearchpath* gladeSearchPath, GtkBox* box): plugin(plugin) {
Builder builder(gladeSearchPath, UI_FILE);
gtk_box_append(box, builder.get(UI_WIDGET_NAME));
stateButton = GTK_CHECK_BUTTON(builder.get("cbEnabled"));
gtk_label_set_text(GTK_LABEL(builder.get("pluginName")), plugin->getName().c_str());
gtk_label_set_text(GTK_LABEL(builder.get("lbAuthor")), plugin->getAuthor().c_str());
gtk_label_set_text(GTK_LABEL(builder.get("lbVersion")), plugin->getVersion().c_str());
gtk_label_set_text(GTK_LABEL(builder.get("lbDescription")), plugin->getDescription().c_str());
gtk_label_set_text(GTK_LABEL(builder.get("lbPath")), char_cast(plugin->getPath().u8string().c_str()));
gtk_label_set_text(GTK_LABEL(builder.get("lbDefaultText")),
plugin->isDefaultEnabled() ? _("default enabled") : _("default disabled"));
gtk_check_button_set_active(stateButton, plugin->isEnabled());
}
void PluginDialogEntry::saveSettings(std::string& pluginEnabled, std::string& pluginDisabled) {
bool state = gtk_check_button_get_active(stateButton);
if (state == plugin->isDefaultEnabled()) {
return;
}
if (state) {
if (!pluginEnabled.empty()) {
pluginEnabled += ",";
}
pluginEnabled.append(char_cast(plugin->getPath().u8string()));
} else {
if (!pluginDisabled.empty()) {
pluginDisabled += ",";
}
pluginDisabled.append(char_cast(plugin->getPath().u8string()));
}
}
#endif
@@ -1,40 +0,0 @@
/*
* Xournal++
*
* Configuration of a single plugin
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include "config-features.h"
#ifdef ENABLE_PLUGINS
#include <string> // for string
#include <gtk/gtk.h>
class Plugin;
class GladeSearchpath;
class PluginDialogEntry {
public:
PluginDialogEntry(Plugin* plugin, GladeSearchpath* gladeSearchPath, GtkBox* box);
public:
void saveSettings(std::string& pluginEnabled, std::string& pluginDisabled);
private:
/**
* Plugin instance
*/
Plugin* plugin;
GtkCheckButton* stateButton;
};
#endif
@@ -1,40 +0,0 @@
#include "RenameLayerDialog.h"
#include <memory> // for allocator, make_unique
#include <string> // for string
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
#include "control/layer/LayerController.h" // for LayerController
#include "gui/Builder.h"
#include "undo/LayerRenameUndoAction.h" // for LayerRenameUndoAction
#include "undo/UndoRedoHandler.h" // for UndoRedoHandler
#include "util/gtk4_helper.h"
class GladeSearchpath;
class Layer;
constexpr auto UI_FILE = "renameLayerDialog.glade";
constexpr auto UI_DIALOG_NAME = "renameLayerDialog";
RenameLayerDialog::RenameLayerDialog(GladeSearchpath* gladeSearchPath, UndoRedoHandler* undo, LayerController* lc,
Layer* l):
lc(lc), undo(undo), l(l) {
Builder builder(gladeSearchPath, UI_FILE);
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
layerNameEntry = GTK_ENTRY(builder.get("layerNameEntry"));
gtk_editable_set_text(GTK_EDITABLE(layerNameEntry), lc->getCurrentLayerName().c_str());
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(+[](RenameLayerDialog* self) {
std::string newName = gtk_editable_get_text(GTK_EDITABLE(self->layerNameEntry));
self->undo->addUndoAction(std::make_unique<LayerRenameUndoAction>(
self->lc, self->l, newName, self->lc->getCurrentLayerName()));
self->lc->setCurrentLayerName(newName);
gtk_window_close(self->window.get());
}),
this);
}
@@ -1,36 +0,0 @@
/*
* Xournal++
*
* The rename layer dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <gtk/gtk.h>
#include "util/raii/GtkWindowUPtr.h"
class GladeSearchpath;
class Layer;
class LayerController;
class UndoRedoHandler;
class RenameLayerDialog {
public:
RenameLayerDialog(GladeSearchpath* gladeSearchPath, UndoRedoHandler* undo, LayerController* lc, Layer* l);
inline GtkWindow* getWindow() const { return window.get(); }
private:
LayerController* lc;
UndoRedoHandler* undo;
Layer* l;
xoj::util::GtkWindowUPtr window;
GtkEntry* layerNameEntry;
};
@@ -1,119 +0,0 @@
#include "SelectBackgroundColorDialog.h"
#include <cstdint> // for uint32_t
#include <string> // for allocator
#include <glib.h> // for g_free, g_strdup_printf
#include "control/Control.h" // for Control
#include "control/settings/Settings.h" // for SElement, Settings
#include "util/Color.h"
#include "util/i18n.h" // for _
#include "util/safe_casts.h" // for as_unsigned
static inline std::array<GdkRGBA, 9> background1 = {
Util::rgb_to_GdkRGBA(Colors::xopp_lightpink), //
Util::rgb_to_GdkRGBA(Colors::xopp_bisque), //
Util::rgb_to_GdkRGBA(Colors::xopp_lemonchifon), //
Util::rgb_to_GdkRGBA(Colors::xopp_palegoldenrod), //
Util::rgb_to_GdkRGBA(Colors::xopp_lavender), //
Util::rgb_to_GdkRGBA(Colors::xopp_gainsboro03), //
Util::rgb_to_GdkRGBA(Colors::xopp_antiquewhite), //
Util::rgb_to_GdkRGBA(Colors::xopp_gainsboro), //
Util::rgb_to_GdkRGBA(Colors::xopp_snow) //
};
static inline std::array<GdkRGBA, 6> backgroundXournal = {
Util::rgb_to_GdkRGBA(Colors::white), //
Util::rgb_to_GdkRGBA(Colors::xopp_paleturqoise), //
Util::rgb_to_GdkRGBA(Colors::xopp_aquamarine), //
Util::rgb_to_GdkRGBA(Colors::xopp_pink), //
Util::rgb_to_GdkRGBA(Colors::xopp_lightsalmon), //
Util::rgb_to_GdkRGBA(Colors::xopp_khaki) //
};
SelectBackgroundColorDialog::SelectBackgroundColorDialog(Control* control, std::function<void(Color)> callback):
control(control), callback(callback) {
lastBackgroundColors.fill(Util::rgb_to_GdkRGBA(Colors::white));
Settings* settings = control->getSettings();
SElement& el = settings->getCustomElement("lastUsedPageBgColor");
int count = 0;
el.getInt("count", count);
auto index = 0ULL;
for (int i = 0; i < count && index < LAST_BACKGROUND_COLOR_COUNT; i++) {
int iColor{};
char* settingName = g_strdup_printf("color%02i", i);
bool read = el.getInt(settingName, iColor);
g_free(settingName);
if (!read) {
continue;
}
lastBackgroundColors[index] = Util::rgb_to_GdkRGBA(Color(as_unsigned(iColor)));
++index;
}
window.reset(GTK_WINDOW(gtk_color_chooser_dialog_new(_("Select background color"), nullptr)));
GtkColorChooser* chooser = GTK_COLOR_CHOOSER(window.get());
gtk_color_chooser_set_use_alpha(chooser, false);
gtk_color_chooser_add_palette(chooser, GTK_ORIENTATION_HORIZONTAL, 9, background1.size(), background1.data());
gtk_color_chooser_add_palette(chooser, GTK_ORIENTATION_HORIZONTAL, 9, backgroundXournal.size(),
backgroundXournal.data());
// Last used colors (only by background, the general last used are shared with the
// pen and highlighter etc.)
gtk_color_chooser_add_palette(chooser, GTK_ORIENTATION_HORIZONTAL, 9, static_cast<int>(lastBackgroundColors.size()),
lastBackgroundColors.data());
g_signal_connect(window.get(), "response",
G_CALLBACK(+[](GtkColorChooser*, int response, SelectBackgroundColorDialog* self) {
if (response == GTK_RESPONSE_DELETE_EVENT) {
// Ignore DELETE_EVENT, as this is caught by the PopupWindowWrapper
return;
}
if (response == GTK_RESPONSE_OK) {
GdkRGBA color;
gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(self->window.get()), &color);
self->storeLastUsedValuesInSettings(color);
self->callback(Util::GdkRGBA_to_argb(color));
}
gtk_window_close(self->window.get());
}),
this);
}
void SelectBackgroundColorDialog::storeLastUsedValuesInSettings(GdkRGBA color) {
for (auto& lastBackgroundColor: lastBackgroundColors) {
if (gdk_rgba_equal(&lastBackgroundColor, &color)) {
// The new color is already in the list, do not save
return;
}
}
Settings* settings = control->getSettings();
SElement& el = settings->getCustomElement("lastUsedPageBgColor");
// Move all colors one step back
for (size_t i = LAST_BACKGROUND_COLOR_COUNT - 1; i > 0; i--) {
lastBackgroundColors[i] = lastBackgroundColors[i - 1];
}
lastBackgroundColors[0] = color;
el.setInt("count", LAST_BACKGROUND_COLOR_COUNT);
for (size_t i = 0; i < LAST_BACKGROUND_COLOR_COUNT; i++) {
char* settingName = g_strdup_printf("color%02zu", i);
el.setIntHex(settingName, int(uint32_t(Util::GdkRGBA_to_argb(lastBackgroundColors[i]))));
g_free(settingName);
}
settings->customSettingsChanged();
}
@@ -1,50 +0,0 @@
/*
* Xournal++
*
* Dialog for selecting a background color
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <array> // for array
#include <functional>
#include <gdk/gdk.h> // for GdkRGBA
#include <gtk/gtk.h> // for GtkWindow
#include "util/Color.h"
#include "util/raii/GtkWindowUPtr.h"
class Control;
/**
* Count of last background colors
*/
const int LAST_BACKGROUND_COLOR_COUNT = 9;
class SelectBackgroundColorDialog {
public:
SelectBackgroundColorDialog(Control* control, std::function<void(Color)> callback);
inline GtkWindow* getWindow() const { return window.get(); }
private:
void storeLastUsedValuesInSettings(GdkRGBA color);
private:
Control* control{nullptr};
/**
* Last used background colors (stored in settings)
*/
std::array<GdkRGBA, LAST_BACKGROUND_COLOR_COUNT> lastBackgroundColors{};
xoj::util::GtkWindowUPtr window;
std::function<void(Color)> callback;
};
@@ -1,121 +0,0 @@
#include "SelectOpacityDialog.h"
#include <cairo.h> // for cairo_set_operator, cairo_rectangle, cairo_...
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
#include <glib.h> // for gdouble
#include "gui/Builder.h"
#include "util/Stacktrace.h"
#include "util/i18n.h"
#include "util/raii/CairoWrappers.h"
#include "util/raii/GObjectSPtr.h"
#include "util/safe_casts.h" // for round_cast
class GladeSearchpath;
constexpr auto UI_FILE = "selectOpacity.glade";
constexpr auto UI_DIALOG_NAME = "selectOpacityDialog";
static int percentToByte(double percent) { return round_cast<int>(percent * 2.55); }
static double byteToPercent(int byte) { return byte / 2.55; }
static inline void buildLabel(Builder& builder, OpacityFeature opacityFeature) {
// Used to set the label of the dialog in the form of:
// <b>{toolOptionsDesc}</b>\n
// Select opacity for: {opacityFeatureDesc}
std::string toolOptionsDesc;
std::string selectOpacityFor = _("Select opacity for: ");
std::string opacityFeatureDesc;
switch (opacityFeature) {
case OPACITY_FILL_HIGHLIGHTER:
toolOptionsDesc = _("Highlighter Options");
opacityFeatureDesc = _("Fill color");
break;
case OPACITY_FILL_PEN:
toolOptionsDesc = _("Pen Options");
opacityFeatureDesc = _("Fill color");
break;
case OPACITY_SELECT_PDF_TEXT_MARKER:
toolOptionsDesc = _("PDF Text Options");
opacityFeatureDesc = _("PDF Text Marker");
break;
default:
g_warning("No opacityFeature description set for '%s'", opacityFeatureToString(opacityFeature).data());
Stacktrace::printStacktrace();
break;
}
gtk_label_set_label(GTK_LABEL(builder.get("label1")),
FC(_F("<b>{1}</b>\n{2}{3}") % toolOptionsDesc % selectOpacityFor % opacityFeatureDesc));
}
xoj::popup::SelectOpacityDialog::SelectOpacityDialog(GladeSearchpath* gladeSearchPath, int alpha,
OpacityFeature feature,
std::function<void(int, OpacityFeature)> callback):
opacityFeature(feature), callback(callback) {
Builder builder(gladeSearchPath, UI_FILE);
this->window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
buildLabel(builder, opacityFeature);
previewImage = GTK_IMAGE(builder.get("imgPreview"));
alphaRange = GTK_RANGE(builder.get("scaleAlpha"));
gtk_range_set_value(alphaRange, byteToPercent(alpha));
setPreviewImage(alpha);
g_signal_connect(alphaRange, "value-changed", G_CALLBACK(+[](GtkRange* range, gpointer self) {
static_cast<SelectOpacityDialog*>(self)->setPreviewImage(
percentToByte(round_cast<int>(gtk_range_get_value(range))));
}),
this);
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
g_signal_connect(builder.get("btOk"), "clicked", G_CALLBACK(+[](GtkButton*, SelectOpacityDialog* self) {
self->callback(percentToByte(gtk_range_get_value(self->alphaRange)), self->opacityFeature);
gtk_window_close(self->window.get());
}),
this);
#if GTK_MAJOR_VERSION == 3
// Widgets are visible by default in gtk4
gtk_widget_show_all(builder.get("dialog-main-box"));
#endif
}
xoj::popup::SelectOpacityDialog::~SelectOpacityDialog() = default;
const int PREVIEW_WIDTH = 70;
const int PREVIEW_HEIGTH = 50;
const int PREVIEW_BORDER = 10;
void xoj::popup::SelectOpacityDialog::setPreviewImage(int alpha) {
xoj::util::CairoSurfaceSPtr surface(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PREVIEW_WIDTH, PREVIEW_HEIGTH),
xoj::util::adopt);
xoj::util::CairoSPtr cairo(cairo_create(surface.get()), xoj::util::adopt);
cairo_t* cr = cairo.get();
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgb(cr, 255, 255, 255);
cairo_paint(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_set_source_rgba(cr, 0, 0x80 / 255.0, 0, alpha / 255.0);
cairo_rectangle(cr, PREVIEW_BORDER, PREVIEW_BORDER, PREVIEW_WIDTH - PREVIEW_BORDER * 2,
PREVIEW_HEIGTH - PREVIEW_BORDER * 2);
cairo_fill(cr);
if (opacityFeature == OPACITY_FILL_PEN) {
cairo_set_line_width(cr, 5);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgb(cr, 0, 0x80 / 255.0, 0);
cairo_rectangle(cr, PREVIEW_BORDER, PREVIEW_BORDER, PREVIEW_WIDTH - PREVIEW_BORDER * 2,
PREVIEW_HEIGTH - PREVIEW_BORDER * 2);
cairo_stroke(cr);
}
xoj::util::GObjectSPtr<GdkPixbuf> pixbuf(
gdk_pixbuf_get_from_surface(surface.get(), 0, 0, PREVIEW_WIDTH, PREVIEW_HEIGTH), xoj::util::adopt);
gtk_image_set_from_pixbuf(previewImage, pixbuf.get());
}
@@ -1,41 +0,0 @@
/*
* Xournal++
*
* The about dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <functional>
#include "control/ToolEnums.h"
#include "util/raii/GtkWindowUPtr.h"
class GladeSearchpath;
namespace xoj::popup {
class SelectOpacityDialog {
public:
SelectOpacityDialog(GladeSearchpath* gladeSearchPath, int alpha, OpacityFeature type,
std::function<void(int, OpacityFeature)> callback);
~SelectOpacityDialog();
inline GtkWindow* getWindow() const { return window.get(); }
private:
void setPreviewImage(int alpha);
private:
xoj::util::GtkWindowUPtr window;
GtkImage* previewImage;
GtkRange* alphaRange;
OpacityFeature opacityFeature;
std::function<void(int, OpacityFeature)> callback;
};
}; // namespace xoj::popup
File diff suppressed because it is too large. Load diff
@@ -1,105 +0,0 @@
/*
* Xournal++
*
* Settings Dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <functional>
#include <string> // for string
#include <vector> // for vector
#include <gtk/gtk.h> // for GtkWidget, GtkWindow
#include "audio/DeviceInfo.h" // for DeviceInfo
#include "control/tools/StrokeStabilizerEnum.h" // for AveragingMethod, Pre...
#include "gui/Builder.h"
#include "util/raii/GtkWindowUPtr.h"
#include "ButtonConfigGui.h"
#include "LanguageConfigGui.h"
#include "LatexSettingsPanel.h"
#include "SettingsDialogPaletteTab.h"
#include "config-features.h" // for ENABLE_AUDIO
#include "filesystem.h" // for path
class Control;
class Settings;
class DeviceTestingArea;
struct Palette;
class SettingsDialog {
public:
SettingsDialog(GladeSearchpath* gladeSearchPath, Settings* settings, Control* control,
const std::vector<fs::path>& paletteDirectories, std::function<void()> callback);
~SettingsDialog();
inline GtkWindow* getWindow() const { return window.get(); }
private:
void save();
void setDpi(int dpi);
/**
* Set active regions
*/
void enableWithCheckbox(const std::string& checkbox, const std::string& widget);
void disableWithCheckbox(const std::string& checkbox, const std::string& widget);
void enableWithEnabledCheckbox(const std::string& checkbox, const std::string& widget);
/*
* Listeners for changes to settings.
*/
void customHandRecognitionToggled();
void customStylusIconTypeChanged();
/**
* Update whether options can be selected, tooltips, etc. for
* pressure sensitivity options (e.g. pressure multiplier).
*/
void updatePressureSensitivityOptions();
private:
void load();
void loadCheckbox(const char* name, bool value);
bool getCheckbox(const char* name);
void loadSlider(const char* name, double value);
double getSlider(const char* name);
void initMouseButtonEvents(GladeSearchpath* gladeSearchPath);
void showStabilizerAvMethodOptions(StrokeStabilizer::AveragingMethod method);
void showStabilizerPreprocessorOptions(StrokeStabilizer::Preprocessor preprocessor);
private:
Settings* settings = nullptr;
Control* control = nullptr;
GtkWidget* callib = nullptr;
int dpi = 72;
#ifdef ENABLE_AUDIO
std::vector<DeviceInfo> audioInputDevices;
std::vector<DeviceInfo> audioOutputDevices;
#endif
Builder builder;
xoj::util::GtkWindowUPtr window;
std::unique_ptr<DeviceTestingArea> deviceTestingArea;
LanguageConfigGui languageConfig;
std::vector<std::unique_ptr<ButtonConfigGui>> buttonConfigs;
LatexSettingsPanel latexPanel;
SettingsDialogPaletteTab paletteTab;
std::function<void()> callback;
};
@@ -1,213 +0,0 @@
#include "SettingsDialogPaletteTab.h"
#include "gui/toolbarMenubar/icon/ColorIcon.h"
#include "util/PathUtil.h"
#include "util/XojMsgBox.h"
#include "util/gtk4_helper.h"
#include "util/i18n.h"
static const char* const G_OBJECT_PALETTE_PATH = "xournalpp.palettePath";
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HELPER
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
auto colorize(const std::string& text, const std::string& color) -> std::string {
return std::string{"<span foreground=\""} + color + std::string{"\">"} + text + std::string{"</span>"};
}
void setGObjectPalettePath(GObject* gObject, const fs::path& path) {
g_object_set_data(gObject, G_OBJECT_PALETTE_PATH, (gpointer)&path);
}
auto getGObjectPalettePath(GObject* gObject) -> fs::path {
auto* pathPointer = (fs::path*)g_object_get_data(gObject, G_OBJECT_PALETTE_PATH);
return *pathPointer;
}
auto getDisplayPalettePath(const fs::path& path) -> std::string {
if (path.parent_path() == Util::getBuiltInPaletteDirectoryPath()) {
return std::string{"└─ "} + "<i>" + FS(_F("Built-in palettes")) + "/</i> " +
char_cast(path.filename().u8string().c_str());
} else if (path.parent_path() == Util::getCustomPaletteDirectoryPath()) {
return std::string{"└─ "} + "<i>" + FS(_F("User palettes")) + "/</i> " +
char_cast(path.filename().u8string().c_str());
} else {
g_warning("Unexpected type of palette path encountered %s", char_cast(path.parent_path().u8string().c_str()));
return std::string{"└─ "} + char_cast(path.u8string().c_str());
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Public Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
constexpr auto UI_FILE = "paletteSettings.glade";
constexpr auto UI_PANEL_NAME = "paletteSettingsPanel";
SettingsDialogPaletteTab::SettingsDialogPaletteTab(GladeSearchpath* gladeSearchPath,
const std::vector<fs::path>& paletteDirectories):
builder(gladeSearchPath, UI_FILE) {
colorPaletteExplainLabel = GTK_LABEL(builder.get("colorPaletteExplainLabel"));
paletteListBox = GTK_LIST_BOX(builder.get("paletteListBox"));
panel = GTK_SCROLLED_WINDOW(builder.get(UI_PANEL_NAME));
renderColorPaletteExplainLabel();
setAllPaletteFilePaths(paletteDirectories);
}
void SettingsDialogPaletteTab::renderPaletteTab(const fs::path& currentlySetPalettePath) {
GtkListBox* lb = paletteListBox;
if (allPaletteFilePaths.empty()) {
renderNoPaletteFoundDisclaimer(lb);
return;
}
for (const fs::path& p: allPaletteFilePaths) {
GtkWidget* listBoxRow = renderPaletteListBoxRow(lb, p);
if (p == currentlySetPalettePath) {
gtk_list_box_select_row(GTK_LIST_BOX(lb), GTK_LIST_BOX_ROW(listBoxRow));
}
}
gtk_widget_show_all(GTK_WIDGET(lb));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Private Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
auto SettingsDialogPaletteTab::renderPaletteListBoxRow(GtkListBox* lb, const fs::path& p) -> GtkWidget* {
Palette palette{p};
GtkWidget* listBoxRow = nullptr;
try {
palette.load();
listBoxRow = newPaletteListBoxRow(palette);
} catch (const std::exception& e) {
listBoxRow = newErrorListBoxRow(p, e.what());
}
setGObjectPalettePath(G_OBJECT(listBoxRow), p);
gtk_list_box_prepend(lb, listBoxRow);
return listBoxRow;
}
void SettingsDialogPaletteTab::renderColorPaletteExplainLabel() const {
gtk_label_set_label(colorPaletteExplainLabel, FS(_F("<i>The palettes shown below are obtained from the "
"<a href=\"{1}\">Built-in palettes</a> and "
"<a href=\"{2}\">User palettes</a> directories:</i>\n") %
Util::toUri(Util::getBuiltInPaletteDirectoryPath()).value() %
Util::toUri(Util::getCustomPaletteDirectoryPath()).value())
.c_str());
gtk_label_set_wrap(colorPaletteExplainLabel, true);
gtk_label_set_use_markup(colorPaletteExplainLabel, true);
g_signal_connect(colorPaletteExplainLabel, "activate-link", G_CALLBACK(+[](GtkLabel*, gchar* uri, gpointer) {
XojMsgBox::openURL(nullptr, uri);
return true; // default handler does not work on Windows
}),
nullptr);
}
void SettingsDialogPaletteTab::renderNoPaletteFoundDisclaimer(GtkListBox* lb) {
GtkWidget* listBoxRow = gtk_list_box_row_new();
GtkWidget* label = gtk_label_new(("<span foreground=\"red\">" +
FS(_F("No palette files (i.e. with extension .gpl) could be found.\n"
"Using the default until another palette is configured.")) +
"</span>")
.c_str());
gtk_label_set_use_markup(GTK_LABEL(label), true);
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(listBoxRow), label);
gtk_list_box_row_set_activatable(GTK_LIST_BOX_ROW(listBoxRow), FALSE);
gtk_list_box_row_set_selectable(GTK_LIST_BOX_ROW(listBoxRow), FALSE);
gtk_list_box_prepend(lb, listBoxRow);
}
// use list of fs::path as input
void SettingsDialogPaletteTab::setAllPaletteFilePaths(const std::vector<fs::path>& paletteDirectories) {
for (const fs::path& paletteDirectory: paletteDirectories) {
std::vector<fs::path> const files = Util::listFilesSorted(paletteDirectory);
for (const fs::path& paletteFile: files) {
if (paletteFile.extension() == ".gpl") {
allPaletteFilePaths.push_back(paletteFile);
}
}
}
}
auto SettingsDialogPaletteTab::getSelectedPalette() const -> std::optional<fs::path> {
GtkListBoxRow* selected_listbox_row = gtk_list_box_get_selected_row(paletteListBox);
if (allPaletteFilePaths.empty()) {
return std::nullopt;
}
if (!G_IS_OBJECT(selected_listbox_row)) {
return std::nullopt;
}
return getGObjectPalettePath(G_OBJECT(selected_listbox_row));
}
auto SettingsDialogPaletteTab::newErrorListBoxRow(const fs::path& palettePath, const std::string& error) -> GtkWidget* {
GtkWidget* listBoxRow = gtk_list_box_row_new();
GtkWidget* rowContent = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(listBoxRow), rowContent);
std::string const formattedError = colorize(FS(_F("Error")) + ": " + error, "red");
GtkWidget* text = newPaletteTextBox(formattedError, palettePath);
gtk_box_append(GTK_BOX(rowContent), text);
return listBoxRow;
}
auto SettingsDialogPaletteTab::newPaletteListBoxRow(Palette& palette) -> GtkWidget* {
GtkWidget* listBoxRow = gtk_list_box_row_new();
GtkWidget* rowContent = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(listBoxRow), rowContent);
std::string const paletteName = palette.getHeader(std::string{FS(_F("Name"))});
GtkWidget* text = nullptr;
if (paletteName.empty()) {
text = newPaletteTextBox(std::string{"<i>" + FS(_F("Palette has no Name")) + "</i>"}, palette.getFilePath());
} else {
text = newPaletteTextBox(paletteName, palette.getFilePath().u8string());
}
gtk_box_append(GTK_BOX(rowContent), text);
GtkWidget* colorIcons = newPaletteColorIconsBox(palette);
gtk_widget_set_hexpand(colorIcons, true);
gtk_box_append(GTK_BOX(rowContent), colorIcons);
return listBoxRow;
}
auto SettingsDialogPaletteTab::newPaletteTextBox(const std::string& mainContent, const fs::path& path) -> GtkWidget* {
GtkWidget* textBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 3);
GtkWidget* mainLabel = gtk_label_new(mainContent.c_str());
gtk_widget_set_halign(mainLabel, GTK_ALIGN_START);
gtk_label_set_use_markup(GTK_LABEL(mainLabel), true);
gtk_box_append(GTK_BOX(textBox), mainLabel);
GtkWidget* secondaryLabel = gtk_label_new(getDisplayPalettePath(path).c_str());
gtk_widget_set_halign(secondaryLabel, GTK_ALIGN_START);
gtk_label_set_use_markup(GTK_LABEL(secondaryLabel), true);
gtk_box_append(GTK_BOX(textBox), secondaryLabel);
return textBox;
}
auto SettingsDialogPaletteTab::newPaletteColorIconsBox(const Palette& palette) -> GtkWidget* {
GtkWidget* colors = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
for (unsigned long i = 0; i < palette.size(); i++) {
const NamedColor& namedColor = palette.getColorAt(i);
const Color c = namedColor.getColor();
GtkWidget* icon = ColorIcon::newGtkImage(c, 16, true);
gtk_box_append(GTK_BOX(colors), icon);
}
gtk_widget_set_halign(colors, GTK_ALIGN_END);
return colors;
}
@@ -1,36 +0,0 @@
#pragma once
#include <filesystem>
#include <optional>
#include <vector>
#include <gtk/gtk.h>
#include "gui/Builder.h"
#include "gui/GladeGui.h"
#include "gui/toolbarMenubar/model/ColorPalette.h"
class SettingsDialogPaletteTab {
public:
SettingsDialogPaletteTab(GladeSearchpath* gladeSearchPath, const std::vector<fs::path>& paletteDirectories);
void renderPaletteTab(const fs::path& currentlySetPalettePath);
auto getSelectedPalette() const -> std::optional<fs::path>;
inline GtkWidget* getPanel() const { return GTK_WIDGET(panel); }
private:
std::vector<fs::path> allPaletteFilePaths;
GtkLabel* colorPaletteExplainLabel;
GtkListBox* paletteListBox;
Builder builder;
GtkScrolledWindow* panel;
void setAllPaletteFilePaths(const std::vector<fs::path>& paletteDirectories);
void renderColorPaletteExplainLabel() const;
static auto newErrorListBoxRow(const fs::path& palettePath, const std::string& error) -> GtkWidget*;
static auto newPaletteTextBox(const std::string& mainContent, const fs::path& path) -> GtkWidget*;
static auto newPaletteColorIconsBox(const Palette& palette) -> GtkWidget*;
static auto newPaletteListBoxRow(Palette& palette) -> GtkWidget*;
static void renderNoPaletteFoundDisclaimer(GtkListBox* lb);
static auto renderPaletteListBoxRow(GtkListBox* lb, const fs::path& p) -> GtkWidget*;
};
@@ -1,187 +0,0 @@
#include "ToolbarManageDialog.h"
#include <string> // for allocator, string
#include <vector> // for vector
#include <glib-object.h> // for g_signal_connect
#include <pango/pango.h> // for PANGO_WEIGHT_NORMAL
#include "gui/Builder.h"
#include "gui/toolbarMenubar/model/ToolbarData.h" // for ToolbarData
#include "gui/toolbarMenubar/model/ToolbarModel.h" // for ToolbarModel
#include "util/Assert.h" // for xoj_assert
#include "util/i18n.h" // for _
class GladeSearchpath;
constexpr auto UI_FILE = "toolbarManageDialog.glade";
constexpr auto UI_DIALOG_NAME = "manageToolbarsDialog";
enum { COLUMN_STRING, COLUMN_BOLD, COLUMN_POINTER, COLUMN_EDITABLE, N_COLUMNS };
static xoj::util::GObjectSPtr<GtkListStore> createModel(ToolbarModel* tb) {
xoj_assert(tb);
GtkTreeIter iter;
GtkListStore* model = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_POINTER, G_TYPE_BOOLEAN);
gtk_list_store_append(model, &iter);
gtk_list_store_set(model, &iter, COLUMN_STRING, _("Predefined"), COLUMN_BOLD, PANGO_WEIGHT_BOLD, COLUMN_POINTER,
nullptr, COLUMN_EDITABLE, false, -1);
for (const auto& data: tb->getToolbars()) {
if (data->isPredefined()) {
gtk_list_store_append(model, &iter);
gtk_list_store_set(model, &iter, COLUMN_STRING, data->getName().c_str(), COLUMN_BOLD, PANGO_WEIGHT_NORMAL,
COLUMN_POINTER, data.get(), COLUMN_EDITABLE, false, -1);
}
}
gtk_list_store_append(model, &iter);
gtk_list_store_set(model, &iter, COLUMN_STRING, _("Customized"), COLUMN_BOLD, PANGO_WEIGHT_BOLD, COLUMN_POINTER,
nullptr, COLUMN_EDITABLE, false, -1);
for (const auto& data: tb->getToolbars()) {
if (!data->isPredefined()) {
gtk_list_store_append(model, &iter);
gtk_list_store_set(model, &iter, COLUMN_STRING, data->getName().c_str(), COLUMN_BOLD, PANGO_WEIGHT_NORMAL,
COLUMN_POINTER, data.get(), COLUMN_EDITABLE, true, -1);
}
}
return xoj::util::GObjectSPtr<GtkListStore>(model, xoj::util::adopt);
}
ToolbarManageDialog::ToolbarManageDialog(GladeSearchpath* gladeSearchPath, ToolbarModel* tbModel,
std::function<void()> callback):
tbModel(tbModel), model(createModel(tbModel)), callback(callback) {
Builder builder(gladeSearchPath, UI_FILE);
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
tree = GTK_TREE_VIEW(builder.get("toolbarList"));
copyButton = builder.get("btCopy");
deleteButton = builder.get("btDelete");
gtk_tree_view_set_model(tree, GTK_TREE_MODEL(this->model.get()));
GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
GtkTreeViewColumn* column =
gtk_tree_view_column_new_with_attributes(_("Toolbars"), renderer, "text", COLUMN_STRING, "weight",
COLUMN_BOLD, "editable", COLUMN_EDITABLE, nullptr);
gtk_tree_view_append_column(tree, column);
GtkTreeSelection* select = gtk_tree_view_get_selection(tree);
gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
g_signal_connect(select, "changed", G_CALLBACK(treeSelectionChangedCallback), this);
g_signal_connect(renderer, "edited", G_CALLBACK(treeCellEditedCallback), this);
g_signal_connect(builder.get("btNew"), "clicked", G_CALLBACK(buttonNewCallback), this);
g_signal_connect(deleteButton, "clicked", G_CALLBACK(buttonDeleteCallback), this);
g_signal_connect(copyButton, "clicked", G_CALLBACK(buttonCopyCallback), this);
g_signal_connect_swapped(builder.get("btClose"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
}
ToolbarManageDialog::~ToolbarManageDialog() { this->callback(); }
void ToolbarManageDialog::buttonNewCallback(GtkButton* button, ToolbarManageDialog* dlg) {
auto data = std::make_unique<ToolbarData>(false);
data->setName(_("New"));
data->setId("custom");
dlg->tbModel->initCopyNameId(data.get());
dlg->addToolbarData(std::move(data));
}
void ToolbarManageDialog::buttonDeleteCallback(GtkButton* button, ToolbarManageDialog* dlg) {
ToolbarData* selected = dlg->getSelectedEntry();
if (selected == nullptr) {
return;
}
GtkTreeModel* model = GTK_TREE_MODEL(dlg->model.get());
GtkTreeIter iter;
if (gtk_tree_model_get_iter_first(model, &iter)) {
do {
ToolbarData* data = nullptr;
gtk_tree_model_get(model, &iter, COLUMN_POINTER, &data, -1);
if (data == selected) {
gtk_list_store_remove(dlg->model.get(), &iter);
break;
}
} while (gtk_tree_model_iter_next(model, &iter));
}
dlg->tbModel->remove(selected);
dlg->updateSelectionData();
}
void ToolbarManageDialog::buttonCopyCallback(GtkButton* button, ToolbarManageDialog* dlg) {
ToolbarData* selected = dlg->getSelectedEntry();
if (selected == nullptr) {
return;
}
auto data = std::make_unique<ToolbarData>(*selected);
dlg->tbModel->initCopyNameId(data.get());
dlg->addToolbarData(std::move(data));
}
void ToolbarManageDialog::addToolbarData(std::unique_ptr<ToolbarData> tbd) {
auto* data = this->tbModel->add(std::move(tbd));
GtkTreeIter iter;
gtk_list_store_append(this->model.get(), &iter);
gtk_list_store_set(this->model.get(), &iter, COLUMN_STRING, data->getName().c_str(), COLUMN_BOLD,
PANGO_WEIGHT_NORMAL, COLUMN_POINTER, data, COLUMN_EDITABLE, true, -1);
GtkTreePath* path = gtk_tree_model_get_path(GTK_TREE_MODEL(this->model.get()), &iter);
GtkTreeViewColumn* column = gtk_tree_view_get_column(tree, 0);
gtk_tree_view_set_cursor(tree, path, column, true);
}
void ToolbarManageDialog::treeCellEditedCallback(GtkCellRendererText* renderer, gchar* pathString, gchar* newText,
ToolbarManageDialog* dlg) {
GtkTreeIter iter;
ToolbarData* data = nullptr;
gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(dlg->model.get()), &iter, pathString);
gtk_tree_model_get(GTK_TREE_MODEL(dlg->model.get()), &iter, COLUMN_POINTER, &data, -1);
if (data) {
gtk_list_store_set(dlg->model.get(), &iter, COLUMN_STRING, newText, -1);
data->setName(newText);
}
}
void ToolbarManageDialog::entrySelected(ToolbarData* data) {
if (data == nullptr) {
gtk_widget_set_sensitive(copyButton, false);
gtk_widget_set_sensitive(deleteButton, false);
} else {
gtk_widget_set_sensitive(copyButton, true);
gtk_widget_set_sensitive(deleteButton, !data->isPredefined());
}
}
auto ToolbarManageDialog::getSelectedEntry() -> ToolbarData* {
GtkTreeIter iter;
GtkTreeModel* model = nullptr;
ToolbarData* data = nullptr;
GtkTreeSelection* selection = gtk_tree_view_get_selection(tree);
if (selection == nullptr) {
return nullptr;
}
if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
gtk_tree_model_get(model, &iter, COLUMN_POINTER, &data, -1);
return data;
}
return nullptr;
}
void ToolbarManageDialog::updateSelectionData() { entrySelected(getSelectedEntry()); }
void ToolbarManageDialog::treeSelectionChangedCallback(GtkTreeSelection* selection, ToolbarManageDialog* dlg) {
dlg->updateSelectionData();
}
@@ -1,60 +0,0 @@
/*
* Xournal++
*
* Toolbar edit dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <functional>
#include <glib.h> // for gchar
#include <gtk/gtk.h> // for GtkButton, GtkCellRendererText, GtkListStore
#include "util/raii/GObjectSPtr.h"
#include "util/raii/GtkWindowUPtr.h"
class ToolbarData;
class ToolbarModel;
class GladeSearchpath;
class ToolbarManageDialog {
public:
ToolbarManageDialog(GladeSearchpath* gladeSearchPath, ToolbarModel* model, std::function<void()> callback);
~ToolbarManageDialog();
public:
inline GtkWindow* getWindow() const { return window.get(); }
private:
static void treeSelectionChangedCallback(GtkTreeSelection* selection, ToolbarManageDialog* dlg);
static void treeCellEditedCallback(GtkCellRendererText* renderer, gchar* pathString, gchar* newText,
ToolbarManageDialog* dlg);
static void buttonNewCallback(GtkButton* button, ToolbarManageDialog* dlg);
static void buttonDeleteCallback(GtkButton* button, ToolbarManageDialog* dlg);
static void buttonCopyCallback(GtkButton* button, ToolbarManageDialog* dlg);
void addToolbarData(std::unique_ptr<ToolbarData> tbd);
void entrySelected(ToolbarData* data);
void updateSelectionData();
ToolbarData* getSelectedEntry();
private:
ToolbarModel* tbModel;
xoj::util::GObjectSPtr<GtkListStore> model;
GtkTreeView* tree;
GtkWidget* copyButton;
GtkWidget* deleteButton;
xoj::util::GtkWindowUPtr window;
std::function<void()> callback;
};
@@ -1,211 +0,0 @@
#include "XojOpenDlg.h"
#include "control/settings/Settings.h" // for Settings
#include "util/PathUtil.h" // for fromGFile, toGFile
#include "util/PopupWindowWrapper.h" // for PopupWindowWrapper
#include "util/Util.h"
#include "util/gtk4_helper.h" // for gtk_file_chooser_set_current_folder
#include "util/i18n.h" // for _
#include "util/raii/GObjectSPtr.h" // for GObjectSPtr
#include "util/raii/GtkWindowUPtr.h" // for GtkWindowUPtr
#include "FileChooserFiltersHelper.h"
static void addlastSavePathShortcut(GtkFileChooser* fc, Settings* settings) {
auto lastSavePath = settings->getLastSavePath();
if (!lastSavePath.empty()) {
#if GTK_MAJOR_VERSION == 3
gtk_file_chooser_add_shortcut_folder(fc, char_cast(lastSavePath.u8string().c_str()), nullptr);
#else
gtk_file_chooser_add_shortcut_folder(fc, Util::toGFile(lastSavePath).get(), nullptr);
#endif
}
}
static void setCurrentFolderToLastOpenPath(GtkFileChooser* fc, Settings* settings) {
xoj::util::GObjectSPtr<GFile> currentFolder;
if (settings && !settings->getLastOpenPath().empty()) {
currentFolder = Util::toGFile(settings->getLastOpenPath());
} else {
currentFolder.reset(g_file_new_for_path(g_get_home_dir()), xoj::util::adopt);
}
gtk_file_chooser_set_current_folder(fc, currentFolder.get(), nullptr);
}
template <class... Args>
static std::function<void(fs::path, Args...)> addSetLastSavePathToCallback(
std::function<void(fs::path, Args...)> callback, Settings* settings) {
return [cb = std::move(callback), settings](fs::path path, Args... args) {
if (settings && !path.empty()) {
settings->setLastOpenPath(path.parent_path());
}
cb(std::move(path), std::forward<Args>(args)...);
};
}
constexpr auto ATTACH_CHOICE_ID = "attachPdfChoice";
static void addAttachChoice(GtkFileChooser* fc) {
gtk_file_chooser_add_choice(fc, ATTACH_CHOICE_ID, _("Attach file to the journal"), nullptr, nullptr);
gtk_file_chooser_set_choice(fc, ATTACH_CHOICE_ID, "false");
}
// Helper class, for a single open dialog
class FileDlg {
public:
/**
* Creates an open file dialog. The callback is only called if a file is actually chosen
* @param callback(path, attachPdf)
*/
FileDlg(const char* title, std::function<void(fs::path, bool)> callback);
/**
* Creates an open file dialog. The callback is only called if a file is actually chosen
* @param callback(path)
*/
FileDlg(const char* title, std::function<void(fs::path)> callback);
~FileDlg() = default;
inline GtkWindow* getWindow() const { return window.get(); }
private:
xoj::util::GtkWindowUPtr window;
std::function<void(fs::path, bool)> callback;
gulong signalId{};
};
static GtkWindow* makeWindow(const char* title) {
// Todo(maybe)
// Restore previews using https://discourse.gnome.org/t/file-chooser-gtk-4-image-preview/11510/2
auto* win = gtk_file_chooser_dialog_new(title, nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, _("_Cancel"),
GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_OK, nullptr);
return GTK_WINDOW(win);
}
FileDlg::FileDlg(const char* title, std::function<void(fs::path, bool)> callback):
window(makeWindow(title)), callback(std::move(callback)) {
this->signalId = g_signal_connect(
window.get(), "response", G_CALLBACK(+[](GtkDialog* win, int response, gpointer data) {
auto* self = static_cast<FileDlg*>(data);
if (response == GTK_RESPONSE_OK) {
auto path =
Util::fromGFile(xoj::util::GObjectSPtr<GFile>(
gtk_file_chooser_get_file(GTK_FILE_CHOOSER(win)), xoj::util::adopt)
.get());
bool attach = false;
if (const char* choice = gtk_file_chooser_get_choice(GTK_FILE_CHOOSER(win), ATTACH_CHOICE_ID);
choice) {
attach = std::strcmp(choice, "true") == 0;
}
// We need to call gtk_window_close() before invoking the callback, because if the callback pops up
// another dialog, the first one won't close...
// So we postpone the callback
Util::execInUiThread([cb = std::move(self->callback), path = std::move(path), attach]() {
cb(std::move(path), attach);
});
}
// Closing the window causes another "response" signal, which we want to ignore
g_signal_handler_disconnect(win, self->signalId);
gtk_window_close(self->getWindow()); // Destroys *self. Beware!
}),
this);
}
FileDlg::FileDlg(const char* title, std::function<void(fs::path)> callback):
FileDlg(title, [cb = std::move(callback)](fs::path path, bool) { cb(std::move(path)); }) {}
void xoj::OpenDlg::showOpenTemplateDialog(GtkWindow* parent, Settings* settings,
std::function<void(fs::path)> callback) {
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(_("Open template file"),
addSetLastSavePathToCallback(std::move(callback), settings));
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
xoj::addFilterAllFiles(fc);
xoj::addFilterXopt(fc);
setCurrentFolderToLastOpenPath(fc, settings);
popup.show(parent);
}
void xoj::OpenDlg::showOpenFileDialog(GtkWindow* parent, Settings* settings, std::function<void(fs::path)> callback) {
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(_("Open file"),
addSetLastSavePathToCallback(std::move(callback), settings));
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
xoj::addFilterSupported(fc);
xoj::addFilterXoj(fc);
xoj::addFilterXopt(fc);
xoj::addFilterXopp(fc);
xoj::addFilterPdf(fc);
xoj::addFilterAllFiles(fc);
addlastSavePathShortcut(fc, settings);
setCurrentFolderToLastOpenPath(fc, settings);
popup.show(parent);
}
void xoj::OpenDlg::showAnnotatePdfDialog(GtkWindow* parent, Settings* settings,
std::function<void(fs::path, bool)> callback) {
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(_("Annotate Pdf file"),
addSetLastSavePathToCallback(std::move(callback), settings));
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
xoj::addFilterPdf(fc);
xoj::addFilterAllFiles(fc);
addlastSavePathShortcut(fc, settings);
setCurrentFolderToLastOpenPath(fc, settings);
addAttachChoice(fc);
popup.show(parent);
}
void xoj::OpenDlg::showOpenImageDialog(GtkWindow* parent, Settings* settings,
std::function<void(fs::path, bool)> callback) {
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(_("Choose image file"),
[cb = std::move(callback), settings](fs::path p, bool attach) {
if (auto folder = p.parent_path(); !folder.empty()) {
settings->setLastImagePath(folder);
}
cb(std::move(p), attach);
});
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
xoj::addFilterImages(fc);
xoj::addFilterAllFiles(fc);
if (!settings->getLastImagePath().empty()) {
gtk_file_chooser_set_current_folder(fc, Util::toGFile(settings->getLastImagePath()).get(), nullptr);
}
addAttachChoice(fc);
popup.show(parent);
}
void xoj::OpenDlg::showMultiFormatDialog(GtkWindow* parent, std::vector<std::string> formats,
std::function<void(fs::path)> callback) {
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(_("Open file"), std::move(callback));
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
if (formats.size() > 0) {
GtkFileFilter* filterSupported = gtk_file_filter_new();
gtk_file_filter_set_name(filterSupported, _("Supported files"));
for (std::string format: formats) {
gtk_file_filter_add_pattern(filterSupported, format.c_str());
}
gtk_file_chooser_add_filter(fc, filterSupported);
}
popup.show(parent);
}
@@ -1,32 +0,0 @@
/*
* Xournal++
*
* GTK Open dialog to select XOJ (or PDF) file with preview
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <functional>
#include <gtk/gtk.h> // for GtkWindow
#include "filesystem.h" // for path
class Settings;
namespace xoj::OpenDlg {
void showOpenFileDialog(GtkWindow* parent, Settings* settings, std::function<void(fs::path)> callback);
/// @param callback(path, attachPdf)
void showAnnotatePdfDialog(GtkWindow* parent, Settings* settings, std::function<void(fs::path, bool)> callback);
void showOpenTemplateDialog(GtkWindow* parent, Settings* settings, std::function<void(fs::path)> callback);
/// @param callback(path, attachImg)
void showOpenImageDialog(GtkWindow* parent, Settings* settings, std::function<void(fs::path, bool)> callback);
void showMultiFormatDialog(GtkWindow* parent, std::vector<std::string> formats, std::function<void(fs::path)> callback);
}; // namespace xoj::OpenDlg
@@ -1,91 +0,0 @@
#include "XojSaveDlg.h"
#include <optional>
#include "control/settings/Settings.h"
#include "util/PathUtil.h" // for fromGFile, toGFile
#include "util/PopupWindowWrapper.h" // for PopupWindowWrapper
#include "util/Util.h"
#include "util/XojMsgBox.h"
#include "util/gtk4_helper.h" // for gtk_file_chooser_set_current_folder
#include "util/i18n.h" // for _
#include "util/raii/GObjectSPtr.h" // for GObjectSPtr
#include "util/raii/GtkWindowUPtr.h" // for GtkWindowUPtr
#include "FileChooserFiltersHelper.h"
static GtkWindow* makeWindow(Settings* settings, fs::path suggestedPath, const char* windowTitle,
const char* buttonLabel) {
GtkWidget* dialog = gtk_file_chooser_dialog_new(windowTitle, nullptr, GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"),
GTK_RESPONSE_CANCEL, buttonLabel, GTK_RESPONSE_OK, nullptr);
if (!suggestedPath.empty()) {
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), Util::toGFile(suggestedPath.parent_path()).get(),
nullptr);
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
Util::toGFilename(suggestedPath.filename()).c_str());
}
if (settings) {
gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), Util::toGFile(settings->getLastOpenPath()).get(),
nullptr);
}
return GTK_WINDOW(dialog);
}
xoj::SaveExportDialog::SaveExportDialog(Settings* settings, fs::path suggestedPath, const char* windowTitle,
const char* buttonLabel,
std::function<bool(fs::path&, const char* filterName)> pathValidation,
std::function<void(std::optional<fs::path>)> callback):
window(makeWindow(settings, std::move(suggestedPath), windowTitle, buttonLabel)),
callback(std::move(callback)),
pathValidation(std::move(pathValidation)) {
this->signalId = g_signal_connect(
window.get(), "response", G_CALLBACK(+[](GtkDialog* win, int response, gpointer data) {
auto* self = static_cast<SaveExportDialog*>(data);
auto* fc = GTK_FILE_CHOOSER(win);
if (response == GTK_RESPONSE_OK) {
auto file = Util::fromGFile(
xoj::util::GObjectSPtr<GFile>(gtk_file_chooser_get_file(fc), xoj::util::adopt).get());
if (self->pathValidation(file, gtk_file_filter_get_name(gtk_file_chooser_get_filter(fc)))) {
XojMsgBox::replaceFileQuestion(
GTK_WINDOW(win), std::move(file),
std::bind(&SaveExportDialog::close, self, std::placeholders::_1));
} // else the dialog stays on until a suitable destination is found or cancel is hit.
} else {
self->close(std::nullopt);
}
}),
this);
}
void xoj::SaveExportDialog::close(std::optional<fs::path> path) {
// We need to call gtk_window_close() before invoking the callback, because if the callback pops up another dialog,
// the first one won't close...
// But since gtk_window_close() triggers the destruction of *this, we first move the callback
auto cb = std::move(this->callback);
// Closing the window causes another "response" signal, which we want to ignore
g_signal_handler_disconnect(window.get(), signalId);
gtk_window_close(window.get()); // Destroys *this. Don't do anything after this call
cb(std::move(path));
}
static bool xoppPathValidation(fs::path& p, const char*) {
Util::clearExtensions(p);
p += ".xopp";
return true;
}
void xoj::SaveExportDialog::showSaveFileDialog(GtkWindow* parent, Settings* settings, fs::path suggestedPath,
std::function<void(std::optional<fs::path>)> callback) {
auto popup = xoj::popup::PopupWindowWrapper<SaveExportDialog>(settings, std::move(suggestedPath), _("Save File"),
_("Save"), xoppPathValidation, std::move(callback));
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
xoj::addFilterXopp(fc);
popup.show(parent);
}
@@ -1,56 +0,0 @@
/*
* Xournal++
*
* GTK Save/Export dialog to select destination file
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <functional>
#include <optional>
#include <gtk/gtk.h> // for GtkWindow
#include "util/raii/GtkWindowUPtr.h"
#include "filesystem.h" // for path
class Settings;
namespace xoj {
/// Helper class, for a single dialog
class SaveExportDialog {
public:
/**
* Shows a save file dialog. The callback is called with std::nullopt if no path were selected
*/
static void showSaveFileDialog(GtkWindow* parent, Settings* settings, fs::path suggestedPath,
std::function<void(std::optional<fs::path>)> callback);
/**
* Creates a save or export file dialog. The callback is called with std::nullopt if no path were selected
* @param pathValidation May modify the given path. Returns true if the path is (now) valid for saving/exporting.
* @param callback(path)
*/
SaveExportDialog(Settings* settings, fs::path suggestedPath, const char* windowTitle, const char* buttonLabel,
std::function<bool(fs::path&, const char* filterName)> pathValidation,
std::function<void(std::optional<fs::path>)> callback);
~SaveExportDialog() = default;
inline GtkWindow* getWindow() const { return window.get(); }
private:
/// Closes the dialog and calls the callback on `path`
void close(std::optional<fs::path> path);
xoj::util::GtkWindowUPtr window;
std::function<void(std::optional<fs::path>)> callback;
std::function<bool(fs::path&, const char* filterName)> pathValidation;
gulong signalId{};
};
}; // namespace xoj
@@ -1,111 +0,0 @@
#include "BackgroundSelectDialogBase.h"
#include <algorithm> // for max
#include <utility> // for move
#include <gdk/gdk.h> // for GDK_EXPOSURE_MASK
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
#include "gui/Builder.h"
#include "util/Util.h" // for paintBackgroundWhite
#include "util/gtk4_helper.h"
#include "util/safe_casts.h" // for round_cast
#include "BaseElementView.h" // for BaseElementView
class GladeSearchpath;
constexpr auto UI_FILE = "backgroundSelection.glade";
constexpr auto UI_DIALOG_NAME = "Dialog";
BackgroundSelectDialogBase::BackgroundSelectDialogBase(GladeSearchpath* gladeSearchPath, Document* doc,
Settings* settings, const char* title):
settings(settings), doc(doc) {
Builder builder(gladeSearchPath, UI_FILE);
this->window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
gtk_window_set_title(window.get(), title);
this->container = GTK_FIXED(gtk_fixed_new());
gtk_widget_set_hexpand(GTK_WIDGET(this->container), true);
this->okButton = builder.get("btOk");
this->vbox = GTK_BOX(builder.get("vbox"));
this->scrolledWindow = GTK_SCROLLED_WINDOW(builder.get("scrollContents"));
gtk_scrolled_window_set_child(scrolledWindow, GTK_WIDGET(container));
g_signal_connect(gtk_scrolled_window_get_hadjustment(scrolledWindow), "notify::page-size",
G_CALLBACK(+[](GObject*, GParamSpec*, gpointer d) {
static_cast<BackgroundSelectDialogBase*>(d)->layout();
}),
this);
gtk_window_set_default_size(this->window.get(), 800, 600);
gtk_widget_set_sensitive(this->okButton, false);
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
g_signal_connect(this->container, "realize", G_CALLBACK(+[](GtkWidget*, gpointer self) {
static_cast<BackgroundSelectDialogBase*>(self)->layout();
}),
this);
}
BackgroundSelectDialogBase::~BackgroundSelectDialogBase() = default;
auto BackgroundSelectDialogBase::getSettings() -> Settings* { return this->settings; }
void BackgroundSelectDialogBase::layout() {
int x = 0;
int y = 0;
int row_height = 0;
auto width = gtk_adjustment_get_page_size(gtk_scrolled_window_get_hadjustment(scrolledWindow));
for (const auto& p: this->entries) {
if (!gtk_widget_get_visible(p->getWidget())) {
continue;
}
if (x + p->getWidth() > width) {
y += row_height;
x = 0;
row_height = 0;
}
gtk_fixed_move(this->container, p->getWidget(), x, y);
row_height = std::max(row_height, p->getHeight());
x += p->getWidth();
}
}
void BackgroundSelectDialogBase::populate() {
for (const auto& e: entries) {
gtk_fixed_put(this->container, e->getWidget(), 0, 0);
}
if (!entries.empty()) {
setSelected(0);
}
layout();
}
void BackgroundSelectDialogBase::setSelected(size_t selected) {
if (this->selected == selected) {
return;
}
size_t lastSelected = this->selected;
if (lastSelected < entries.size()) {
entries[lastSelected]->setSelected(false);
}
if (selected < entries.size()) {
entries[selected]->setSelected(true);
this->selected = selected;
gtk_widget_set_sensitive(okButton, true);
}
}
void BackgroundSelectDialogBase::ok() { gtk_button_clicked(GTK_BUTTON(okButton)); }
@@ -1,64 +0,0 @@
/*
* Xournal++
*
* Base class for Background selection dialog
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <memory>
#include <vector> // for vector
#include <gtk/gtk.h> // for GtkWidget, GtkWindow
#include "util/Util.h" // for npos
#include "util/raii/GtkWindowUPtr.h"
class Document;
class Settings;
class BaseElementView;
class GladeSearchpath;
class BackgroundSelectDialogBase {
protected:
BackgroundSelectDialogBase(GladeSearchpath* gladeSearchPath, Document* doc, Settings* settings, const char* title);
~BackgroundSelectDialogBase();
public:
Settings* getSettings();
void setSelected(size_t selected);
void ok(); ///< As if the Ok button was clicked
inline GtkWindow* getWindow() const { return window.get(); }
protected:
/// Adds the entry widgets
void populate();
void layout();
protected:
xoj::util::GtkWindowUPtr window;
Settings* settings = nullptr;
GtkScrolledWindow* scrolledWindow = nullptr;
GtkFixed* container = nullptr; ///< Area where miniatures are layed out
GtkWidget* okButton = nullptr;
GtkBox* vbox = nullptr; ///< Vertical GtkBox containing all.
Document* doc = nullptr;
/// Selection confirmed
bool confirmed = false;
/// Selected entry, none if npos
size_t selected = npos;
/// Entries to display
std::vector<std::unique_ptr<BaseElementView>> entries;
};
Loaded 100 of 634 files, more files were not shown because too many files have changed in this diff. Show more