mirror of https://github.com/tuhiproject/tuhi.git
Merge tuhigui into gui
This is a plain subtree merge without any changes to the tuhigui tree itself. As we'll keep the distinction between the DBus server and the GTK UI, the directory layout will reflect that.pull/145/head
commit
1f0709eea0
|
@ -0,0 +1,4 @@
|
|||
*~
|
||||
*.swp
|
||||
__pycache__
|
||||
.flatpak-builder
|
|
@ -0,0 +1,340 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin St, 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 Library 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 St, 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 Library General
|
||||
Public License instead of this License.
|
|
@ -0,0 +1,50 @@
|
|||
TuhiGui
|
||||
=======
|
||||
|
||||
Tuhi is a GUI to the Tuhi DBus daemon that connects to and fetches the data
|
||||
from the Wacom ink range (Spark, Slate, Folio, Intuos Paper, ...). The data
|
||||
is converted to SVG and users can save it on disk.
|
||||
|
||||
For more info about Tuhi see: https://github.com/tuhiproject/tuhi
|
||||
|
||||
|
||||
Building TuhiGUI
|
||||
----------------
|
||||
|
||||
```
|
||||
$> git clone http://github.com/tuhiproject/tuhigui
|
||||
$> cd tuhigui
|
||||
$> meson builddir
|
||||
$> ninja -C builddir
|
||||
$> ./builddir/tuhigui.devel
|
||||
```
|
||||
|
||||
TuhiGui requires Python v3.6 or above.
|
||||
|
||||
Install TuhiGUI
|
||||
---------------
|
||||
|
||||
```
|
||||
$> git clone http://github.com/tuhiproject/tuhigui
|
||||
$> cd tuhigui
|
||||
$> meson builddir
|
||||
$> ninja -C builddir install
|
||||
$> tuhigui
|
||||
```
|
||||
|
||||
TuhiGui requires Python v3.6 or above.
|
||||
|
||||
Flatpak
|
||||
-------
|
||||
|
||||
```
|
||||
$> git clone http://github.com/tuhiproject/tuhigui
|
||||
$> cd tuhigui
|
||||
$> flatpak-builder flatpak_builddir org.freedesktop.TuhiGui.json --install --user --force-clean
|
||||
$> flatpak run org.freedesktop.TuhiGui
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
TuhiGui is licensed under the GPLv2 or later.
|
|
@ -0,0 +1,42 @@
|
|||
gnome = import('gnome')
|
||||
|
||||
desktopdir = join_paths(datadir, 'applications')
|
||||
icondir = join_paths(datadir, 'icons', 'hicolor', 'scalable', 'apps')
|
||||
metainfodir = join_paths(datadir, 'metainfo')
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set('version', meson.project_version())
|
||||
conf.set('url', 'https://github.com/tuhiproject/tuhigui')
|
||||
conf.set('version_date', version_date)
|
||||
|
||||
about_dialog = configure_file(input: 'ui/AboutDialog.ui.in',
|
||||
output: 'AboutDialog.ui',
|
||||
configuration: conf)
|
||||
|
||||
install_data('org.freedesktop.TuhiGui.svg', install_dir: icondir)
|
||||
|
||||
i18n.merge_file(input: 'org.freedesktop.TuhiGui.desktop.in',
|
||||
output: 'org.freedesktop.TuhiGui.desktop',
|
||||
type: 'desktop',
|
||||
po_dir: podir,
|
||||
install: true,
|
||||
install_dir: desktopdir)
|
||||
|
||||
appdata = configure_file(input: 'org.freedesktop.TuhiGui.appdata.xml.in.in',
|
||||
output: 'org.freedesktop.TuhiGui.appdata.xml.in',
|
||||
configuration: conf)
|
||||
|
||||
i18n.merge_file(input: appdata,
|
||||
output: 'org.freedesktop.TuhiGui.appdata.xml',
|
||||
type: 'xml',
|
||||
po_dir: podir,
|
||||
install: true,
|
||||
install_dir: metainfodir)
|
||||
|
||||
|
||||
gnome.compile_resources('tuhigui', 'tuhigui.gresource.xml',
|
||||
source_dir: '.',
|
||||
dependencies: [about_dialog],
|
||||
gresource_bundle: true,
|
||||
install: true,
|
||||
install_dir: pkgdatadir)
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>org.freedesktop.TuhiGui</id>
|
||||
<metadata_license>FSFAP</metadata_license>
|
||||
<project_license>GPL-2.0+</project_license>
|
||||
<content_rating type="oars-1.0" />
|
||||
<name>TuhiGui</name>
|
||||
<summary>Utility to download drawings from the Wacom Ink range of devices</summary>
|
||||
<description>
|
||||
<p>
|
||||
TuhiGui is a graphical user interface to download drawings stored on
|
||||
tablet devices from the Wacom Ink range, e.g. Intuos Pro Paper or
|
||||
Bamboo Slate.
|
||||
</p>
|
||||
<p>
|
||||
TuhiGui requires Tuhi, the daemon to actually communicate with the
|
||||
devices. ThiGui is merely a front end to Tuhi, Tuhi must be
|
||||
installed and running when TuhiGui is launched.
|
||||
</p>
|
||||
</description>
|
||||
|
||||
<kudos>
|
||||
<kudo>AppMenu</kudo>
|
||||
<kudo>HiDpiIcon</kudo>
|
||||
<kudo>ModernToolkit</kudo>
|
||||
</kudos>
|
||||
|
||||
<launchable type="desktop-id">org.freedesktop.TuhiGui.desktop</launchable>
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>The button configuraton page</caption>
|
||||
<image>https://github.com/libratbag/piper/raw/wiki/screenshots/flathub/piper-buttonpage.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>The LED configuraton page</caption>
|
||||
<image>https://github.com/libratbag/piper/raw/wiki/screenshots/flathub/piper-ledpage.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>The resolution configuraton page</caption>
|
||||
<image>https://github.com/libratbag/piper/raw/wiki/screenshots/flathub/piper-resolutionpage.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<url type="homepage">https://github.com/tuhiproject/tuhigui/</url>
|
||||
<url type="bugtracker">https://github.com/tuhiproject/tuhigui/issues</url>
|
||||
<url type="help">https://github.com/tuhiproject/tuhigui/wiki</url>
|
||||
<project_group>GNOME</project_group>
|
||||
|
||||
<translation type="gettext">tuhigui</translation>
|
||||
|
||||
<provides>
|
||||
<binary>tuhigui</binary>
|
||||
</provides>
|
||||
|
||||
<releases>
|
||||
<release version="@version@" date="@version_date@"/>
|
||||
</releases>
|
||||
</component>
|
|
@ -0,0 +1,11 @@
|
|||
[Desktop Entry]
|
||||
Name=TuhiGui
|
||||
Comment=Utility to download drawings from the Wacom Ink range of devices
|
||||
Exec=tuhigui
|
||||
# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
|
||||
Icon=org.freedesktop.TuhiGui
|
||||
Type=Application
|
||||
StartupNotify=true
|
||||
Categories=GTK;GNOME;Utility;
|
||||
# Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
|
||||
Keywords=tablet;wacom;ink;
|
|
@ -0,0 +1,124 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="org.freedesktop.TuhiGui.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.49497475"
|
||||
inkscape:cx="-40.019796"
|
||||
inkscape:cy="593.90888"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
ry="5.1324711"
|
||||
rx="5.7588129"
|
||||
y="32.736401"
|
||||
x="22.058018"
|
||||
height="240.71727"
|
||||
width="179.96317"
|
||||
id="rect871"
|
||||
style="fill:#d1d1d1;fill-opacity:1;stroke:#000000;stroke-width:5.85741472;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#f8f8f8;fill-opacity:1;stroke:#000000;stroke-width:4.56920719;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect869"
|
||||
width="125.8467"
|
||||
height="209.46855"
|
||||
x="66.17778"
|
||||
y="51.012478"
|
||||
rx="4.0270891"
|
||||
ry="4.4661989" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:4.07743645;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 115.44566,153.57828 c -4.40264,6.5677 -10.62856,11.54273 -17.558895,15.19366 -4.67694,2.6307 -11.830989,10.60781 -6.551949,15.24865 7.445719,1.96193 15.315034,0.39747 22.254454,-2.65498 7.10422,-3.13141 18.42523,-0.36024 16.74675,9.32995 -2.37548,7.66511 -10.18845,11.53571 -14.93484,17.43238 l -0.47636,1.28811 0.0592,1.3909"
|
||||
id="path843"
|
||||
inkscape:connector-curvature="0" />
|
||||
<circle
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:4.56920719;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path873"
|
||||
cx="43.762691"
|
||||
cy="160.30058"
|
||||
r="8.0724049" />
|
||||
<g
|
||||
id="g951"
|
||||
transform="translate(-0.29412526,-24.83401)">
|
||||
<g
|
||||
transform="matrix(0.58929876,0.50248108,-0.50248108,0.58929876,143.7747,-3.51721)"
|
||||
id="g830">
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect819"
|
||||
width="16.570711"
|
||||
height="104.50238"
|
||||
x="116.52951"
|
||||
y="81.046227"
|
||||
ry="5.7670002"
|
||||
rx="5.1999998" />
|
||||
<path
|
||||
sodipodi:nodetypes="scccccs"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path821"
|
||||
d="m 123.14735,202.7875 c -1.68724,-0.16712 -3.66116,-6.50505 -3.66116,-6.50505 l -6.29172,-10.73384 7.46411,0.0819 12.44164,-0.0819 -5.80754,9.44957 c 0,0 -2.71186,7.9313 -4.14533,7.78932 z"
|
||||
inkscape:transform-center-y="2.8731452"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
transform="matrix(0.83245854,0,0,0.99999988,22.299806,-5.8799047)" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect825"
|
||||
width="7.2162771"
|
||||
height="7.2162771"
|
||||
x="121.07309"
|
||||
y="78.106255"
|
||||
rx="5.1999884"
|
||||
ry="5.7669902" />
|
||||
</g>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path929"
|
||||
d="m 126.29892,160.02213 44.11346,-51.29277"
|
||||
style="fill:none;stroke:#878787;stroke-width:1.59743071;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.0 KiB |
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/freedesktop/TuhiGui">
|
||||
<file preprocess="xml-stripblanks">AboutDialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/Drawing.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/DrawingPerspective.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/MainWindow.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/SetupPerspective.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/ErrorPerspective.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.20.0 -->
|
||||
<interface domain="tuhigui">
|
||||
<requires lib="gtk+" version="3.4"/>
|
||||
<object class="GtkAboutDialog" id="about_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="type_hint">normal</property>
|
||||
<property name="program_name">TuhiGUI</property>
|
||||
<property name="version">@version@</property>
|
||||
<property name="copyright">Copyright © 2019 Tuhi Developers</property>
|
||||
<property name="website">@url@</property>
|
||||
<property name="website_label" translatable="yes">Visit Tuhi’s website</property>
|
||||
<property name="authors">Maintainers:
|
||||
Peter Hutterer <peter.hutterer@who-t.net>
|
||||
|
||||
<!-- Contributor list: git log | git shortlog \-\-summary | cut -f2 -->
|
||||
Contributors:
|
||||
</property>
|
||||
<property name="artists">Peter Hutterer <peter.hutterer@who-t.net></property>
|
||||
<property name="translator_credits" translatable="yes">translator-credits</property>
|
||||
<property name="logo_icon_name">org.freedesktop.TuhiGui</property>
|
||||
<property name="license_type">gpl-2-0</property>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -0,0 +1,148 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkImage" id="icon_download">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-save-as-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="icon_remove">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-delete-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="image_rotate_left">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">object-rotate-left-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="image_rotate_right">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">object-rotate-right-symbolic</property>
|
||||
</object>
|
||||
<template class="Drawing" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">20</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_bottom">20</property>
|
||||
<property name="hexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="btn_remove">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="image">icon_remove</property>
|
||||
<signal name="clicked" handler="_on_delete_button_clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label_timestamp">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">20</property>
|
||||
<property name="margin_right">20</property>
|
||||
<property name="label" translatable="yes">Wed 09:55 07/07/2019</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="semibold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btn_rotate_left">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="image">image_rotate_left</property>
|
||||
<signal name="clicked" handler="_on_rotate_button_clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btn_rotate_right">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="image">image_rotate_right</property>
|
||||
<signal name="clicked" handler="_on_rotate_button_clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btn_download">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="image">icon_download</property>
|
||||
<signal name="clicked" handler="_on_download_button_clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="image_svg">
|
||||
<property name="width_request">200</property>
|
||||
<property name="height_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-missing-image</property>
|
||||
<style>
|
||||
<class name="bg-white"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">baseline</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,219 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<template class="DrawingPerspective" parent="GtkStack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="baseline_position">top</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner_sync">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="label" translatable="yes">last synchronized:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label_last_sync">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="image_battery">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="icon_name">battery-missing-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkOverlay">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="flowbox_drawings">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="index">-1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="overlay">
|
||||
<object class="GtkRevealer" id="overlay_undo">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="transition_type">none</property>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="margin_left">12</property>
|
||||
<property name="margin_right">4</property>
|
||||
<property name="margin_start">12</property>
|
||||
<property name="margin_end">4</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="notification_delete_undo">
|
||||
<property name="label" translatable="yes">Undo delete drawing</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="margin_right">6</property>
|
||||
<property name="margin_end">6</property>
|
||||
<signal name="clicked" handler="_on_undo_clicked" swapped="no"/>
|
||||
<style>
|
||||
<class name="text-button"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="notification_delete_close">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">none</property>
|
||||
<signal name="clicked" handler="_on_undo_close_clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">window-close-symbolic</property>
|
||||
<property name="icon_size">2</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="image-button"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="app-notification"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label_sync">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Press the button on device to synchronize drawings</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">page0</property>
|
||||
<property name="title" translatable="yes">page0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,117 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<template class="ErrorPerspective" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">TuhiGUI is an interactive GUI to download data from Tuhi.
|
||||
|
||||
Tuhi connects to tablets of the Wacom Ink range. It allows you to download the drawings stored on those devices as SVGs for processing later.
|
||||
|
||||
Tuhi is a DBus server that needs to be running for the Tuhi GUI to connect to it. Connecting to the DBus server should take less than a second. If you read this far, your Tuhi DBus server is not running or responsing and needs to be restarted.</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="max_width_chars">55</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_top">20</property>
|
||||
<property name="margin_bottom">20</property>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">30</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Connecting to Tuhi</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">This should take less than a second. Make sure the Tuhi DBus server is running.</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="max_width_chars">55</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<template class="MainWindow" parent="GtkApplicationWindow">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="default_width">1000</property>
|
||||
<property name="default_height">700</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="headerbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child type="title">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="menubutton1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">open-menu-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack_perspectives">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="transition_type">crossfade</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,275 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<template class="SetupDialog" parent="GtkDialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Initial Device Setup</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="gravity">center</property>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btn_quit">
|
||||
<property name="label" translatable="yes">Quit</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_bottom">100</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="baseline_position">bottom</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="label" translatable="yes">Hold the button on the device until the blue light is flashing.</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="wrap">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">20</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Searching for device</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">page0</property>
|
||||
<property name="title" translatable="yes">page0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="baseline_position">bottom</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label_devicename_p1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="label" translatable="yes">Connecting to LE Paper</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="max_width_chars">55</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">20</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Connecting to device...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">page1</property>
|
||||
<property name="title" translatable="yes">page1</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="baseline_position">bottom</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="label" translatable="yes">Press the button on the device now!</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">20</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">waiting for reply</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">page2</property>
|
||||
<property name="title" translatable="yes">page2</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="-6">btn_quit</action-widget>
|
||||
</action-widgets>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,111 @@
|
|||
project('tuhigui',
|
||||
version: '0.1',
|
||||
license: 'GPLv2',
|
||||
meson_version: '>= 0.48.0')
|
||||
# The tag date of the project_version(), update when the version bumps.
|
||||
version_date='2019-07-10'
|
||||
# Note: Update the Contributor list in data/ui/AboutDialog.ui.in when the
|
||||
# version bumps
|
||||
|
||||
# Dependencies
|
||||
dependency('python3', required: true)
|
||||
dependency('pygobject-3.0', required: true)
|
||||
|
||||
# Gtk version required
|
||||
gtk_major_version = 3
|
||||
gtk_minor_version = 22
|
||||
|
||||
prefix = get_option('prefix')
|
||||
datadir = join_paths(prefix, get_option('datadir'))
|
||||
localedir = join_paths(prefix, get_option('localedir'))
|
||||
pkgdatadir = join_paths(datadir, meson.project_name())
|
||||
bindir = join_paths(prefix, get_option('bindir'))
|
||||
podir = join_paths(meson.source_root(), 'po')
|
||||
desktopdir = join_paths(datadir, 'applications')
|
||||
icondir = join_paths(datadir, 'icons', 'hicolor', 'scalable', 'apps')
|
||||
metainfodir = join_paths(datadir, 'metainfo')
|
||||
|
||||
i18n = import('i18n')
|
||||
|
||||
subdir('data')
|
||||
subdir('po')
|
||||
|
||||
# Find the directory to install our Python code
|
||||
pymod = import('python')
|
||||
py3 = pymod.find_installation()
|
||||
python_dir = py3.get_install_dir()
|
||||
install_subdir('tuhigui', install_dir: python_dir, exclude_directories: '__pycache__')
|
||||
|
||||
config_tuhigui = configuration_data()
|
||||
config_tuhigui.set('pkgdatadir', pkgdatadir)
|
||||
config_tuhigui.set('localedir', localedir)
|
||||
config_tuhigui.set('gtk_major_version', gtk_major_version)
|
||||
config_tuhigui.set('gtk_minor_version', gtk_minor_version)
|
||||
config_tuhigui.set('devel', '')
|
||||
|
||||
config_tuhigui_devel = config_tuhigui
|
||||
config_tuhigui_devel.set('pkgdatadir', join_paths(meson.build_root(), 'data'))
|
||||
config_tuhigui_devel.set('localedir', join_paths(meson.build_root(), 'po'))
|
||||
config_tuhigui_devel.set('devel', '''
|
||||
sys.path.insert(1, '@0@')
|
||||
print('Running from source tree, using local files')
|
||||
'''.format(meson.source_root()))
|
||||
|
||||
configure_file(input: 'tuhigui.in',
|
||||
output: 'tuhigui',
|
||||
configuration: config_tuhigui,
|
||||
install_dir: bindir)
|
||||
|
||||
configure_file(input: 'tuhigui.in',
|
||||
output: 'tuhigui.devel',
|
||||
configuration: config_tuhigui_devel)
|
||||
|
||||
meson.add_install_script('meson_install.sh')
|
||||
|
||||
desktop_file = i18n.merge_file(input: 'data/org.freedesktop.TuhiGui.desktop.in',
|
||||
output: 'org.freedesktop.TuhiGui.desktop',
|
||||
type: 'desktop',
|
||||
po_dir: podir,
|
||||
install: true,
|
||||
install_dir: desktopdir)
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set('version', meson.project_version())
|
||||
conf.set('url', 'https://github.com/tuhiproject/tuhigui')
|
||||
conf.set('version_date', version_date)
|
||||
|
||||
appdata_intl = configure_file(input: 'data/org.freedesktop.TuhiGui.appdata.xml.in.in',
|
||||
output: 'org.freedesktop.TuhiGui.appdata.xml.in',
|
||||
configuration: conf)
|
||||
|
||||
appdata = i18n.merge_file(input: appdata_intl,
|
||||
output: 'org.freedesktop.TuhiGui.appdata.xml',
|
||||
type: 'xml',
|
||||
po_dir: podir,
|
||||
install: true,
|
||||
install_dir: metainfodir)
|
||||
|
||||
install_data('data/org.freedesktop.TuhiGui.svg', install_dir: icondir)
|
||||
|
||||
flake8 = find_program('flake8-3', required: false)
|
||||
if flake8.found()
|
||||
test('flake8', flake8,
|
||||
args: ['--ignore=E501,W504',
|
||||
join_paths(meson.source_root(), 'tuhigui/')])
|
||||
endif
|
||||
|
||||
desktop_validate = find_program('desktop-file-validate', required: false)
|
||||
if desktop_validate.found()
|
||||
test('desktop-file-validate', desktop_validate, args: [desktop_file])
|
||||
endif
|
||||
|
||||
appstream_util = find_program('appstream-util', required: false)
|
||||
if appstream_util.found()
|
||||
test('appstream-util validate-relax', appstream_util,
|
||||
args: ['validate-relax', appdata])
|
||||
endif
|
||||
|
||||
# A wrapper to start tuhi at the same time as tuhigui, used by the flatpak
|
||||
configure_file(input: 'tools/tuhi-gui-flatpak.py',
|
||||
output: 'tuhi-gui-flatpak.py',
|
||||
copy: true)
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
if [ -z $DESTDIR ]; then
|
||||
PREFIX=${MESON_INSTALL_PREFIX:-/usr}
|
||||
|
||||
# Update icon cache
|
||||
gtk-update-icon-cache -f -t $PREFIX/share/icons/hicolor
|
||||
|
||||
# Install new schemas
|
||||
#glib-compile-schemas $PREFIX/share/glib-2.0/schemas/
|
||||
fi
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"app-id": "org.freedesktop.TuhiGui",
|
||||
"runtime": "org.gnome.Platform",
|
||||
"runtime-version": "3.30",
|
||||
"sdk": "org.gnome.Sdk",
|
||||
"command": "tuhi-gui",
|
||||
"finish-args": [
|
||||
"--share=ipc",
|
||||
"--socket=x11",
|
||||
"--talk-name=org.freedesktop.tuhi1",
|
||||
"--own-name=org.freedesktop.tuhi1",
|
||||
"--system-talk-name=org.bluez"
|
||||
],
|
||||
"modules": [
|
||||
{
|
||||
"name": "pyxdg",
|
||||
"buildsystem": "simple",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "git://anongit.freedesktop.org/xdg/pyxdg"
|
||||
}
|
||||
],
|
||||
"build-commands": [
|
||||
"pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} ."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "python-pyparsing",
|
||||
"buildsystem": "simple",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/pyparsing/pyparsing/releases/download/pyparsing_2.4.0/pyparsing-2.4.0.tar.gz",
|
||||
"sha512": "71877dc006cce5c1b1d45e7cc89cd60e03cb80353387fb0c6498cfc0d69af465dc574d1bceb87248033e7a82694aa940e9fce1ca80b2ef538a8df51f697ef530"
|
||||
}
|
||||
],
|
||||
"build-commands": [
|
||||
"pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} ."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "python-svgwrite",
|
||||
"buildsystem": "simple",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/mozman/svgwrite.git"
|
||||
}
|
||||
],
|
||||
"build-commands": [
|
||||
"pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} ."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tuhi",
|
||||
"buildsystem": "simple",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/tuhiproject/tuhi"
|
||||
}
|
||||
],
|
||||
"build-commands": [
|
||||
"pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} ."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tuhigui",
|
||||
"buildsystem": "meson",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "."
|
||||
}
|
||||
],
|
||||
"post-install": [
|
||||
"cp tuhi-gui-flatpak.py /app/bin/tuhi-gui",
|
||||
"chmod +x /app/bin/tuhi-gui"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
# Language list must be in alphabetical order
|
|
@ -0,0 +1,17 @@
|
|||
data/org.freedesktop.TuhiGui.appdata.xml.in.in
|
||||
data/org.freedesktop.TuhiGui.desktop.in
|
||||
|
||||
data/ui/AboutDialog.ui.in
|
||||
data/ui/Drawing.ui
|
||||
data/ui/DrawingPerspective.ui
|
||||
data/ui/ErrorPerspective.ui
|
||||
data/ui/MainWindow.ui
|
||||
data/ui/SetupPerspective.ui
|
||||
|
||||
tuhigui/application.py
|
||||
tuhigui/config.py
|
||||
tuhigui/drawing.py
|
||||
tuhigui/drawingperspective.py
|
||||
tuhigui/svg.py
|
||||
tuhigui/tuhi.py
|
||||
tuhigui/window.py
|
|
@ -0,0 +1,37 @@
|
|||
i18n
|
||||
====
|
||||
|
||||
This directory contains the translations of TuhiGui.
|
||||
|
||||
For errors in translations, please [file an
|
||||
issue](https://github.com/tuhiproject/tuhi/issues/new).
|
||||
|
||||
New or updated translations are always welcome. To start a new translation, run:
|
||||
|
||||
$ meson translation-build
|
||||
$ ninja -C translation-build tuhigui-pot
|
||||
# Now you can optionally remove the build directory
|
||||
$ rm -rf translation-build
|
||||
$ cp po/tuhigui.pot po/$lang.po
|
||||
|
||||
where `$lang` is the language code of your target language, e.g. `nl` for Dutch
|
||||
or `en_GB` for British English. Edit the
|
||||
[LINGUAS](https://github.com/tuhiproject/tuhigui/blob/master/po/LINGUAS) file and
|
||||
add your language code, keeping the list sorted alphabetically. Finally, open
|
||||
the `.po` file you just created and translate all the strings. Don't forget to
|
||||
fill in the information in the header!
|
||||
|
||||
To update an existing translation, run:
|
||||
|
||||
$ meson translation-build
|
||||
$ ninja -C translation-build tuhigui-update-po
|
||||
# Now you can optionally remove the build directory
|
||||
$ rm -rf translation-build
|
||||
|
||||
and update the `po/$lang.po` file of your target language.
|
||||
|
||||
When you are done translating, file a pull request on
|
||||
[GitHub](https://github.com/tuhiproject/tuhi) or, if you don't know how to, [open
|
||||
an issue](https://github.com/tuhiproject/tuhi/issues/new) and attach the `.po`
|
||||
file there.
|
||||
|
|
@ -0,0 +1 @@
|
|||
i18n.gettext('tuhigui', preset: 'glib')
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import subprocess
|
||||
from multiprocessing import Process
|
||||
|
||||
def start_tuhi():
|
||||
subprocess.run('tuhi')
|
||||
|
||||
def start_tuhigui():
|
||||
subprocess.run('tuhigui')
|
||||
|
||||
if __name__ == '__main__':
|
||||
tuhi = Process(target=start_tuhi)
|
||||
tuhi.daemon = True
|
||||
tuhi.start()
|
||||
tuhigui = Process(target=start_tuhigui)
|
||||
tuhigui.start()
|
||||
tuhigui.join()
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import gi
|
||||
import sys
|
||||
import os
|
||||
|
||||
gi.require_version('Gio', '2.0')
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gio, Gtk, Gdk
|
||||
|
||||
@devel@
|
||||
resource = Gio.resource_load(os.path.join('@pkgdatadir@', 'tuhigui.gresource'))
|
||||
Gio.Resource._register(resource)
|
||||
|
||||
def install_excepthook():
|
||||
old_hook = sys.excepthook
|
||||
def new_hook(etype, evalue, etb):
|
||||
old_hook(etype, evalue, etb)
|
||||
while Gtk.main_level():
|
||||
Gtk.main_quit()
|
||||
sys.exit()
|
||||
sys.excepthook = new_hook
|
||||
|
||||
def gtk_style():
|
||||
css = b"""
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
}
|
||||
"""
|
||||
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_data(css)
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
Gdk.Screen.get_default(),
|
||||
style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import gettext
|
||||
import locale
|
||||
import signal
|
||||
from tuhigui.application import Application
|
||||
|
||||
install_excepthook()
|
||||
gtk_style()
|
||||
|
||||
locale.bindtextdomain('tuhigui', '@localedir@')
|
||||
locale.textdomain('tuhigui')
|
||||
gettext.bindtextdomain('tuhigui', '@localedir@')
|
||||
gettext.textdomain('tuhigui')
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
exit_status = Application().run(sys.argv)
|
||||
sys.exit(exit_status)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from gi.repository import Gio, GLib, Gtk
|
||||
from .window import MainWindow
|
||||
|
||||
import gi
|
||||
gi.require_version("Gio", "2.0")
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
|
||||
class Application(Gtk.Application):
|
||||
def __init__(self):
|
||||
super().__init__(application_id='org.freedesktop.TuhiGui',
|
||||
flags=Gio.ApplicationFlags.FLAGS_NONE)
|
||||
GLib.set_application_name('TuhiGui')
|
||||
self._tuhi = None
|
||||
|
||||
def do_startup(self):
|
||||
Gtk.Application.do_startup(self)
|
||||
self._build_app_menu()
|
||||
|
||||
def do_activate(self):
|
||||
window = MainWindow(application=self)
|
||||
window.present()
|
||||
|
||||
def _build_app_menu(self):
|
||||
actions = [('about', self._about),
|
||||
('quit', self._quit),
|
||||
('help', self._help)]
|
||||
for (name, callback) in actions:
|
||||
action = Gio.SimpleAction.new(name, None)
|
||||
action.connect('activate', callback)
|
||||
self.add_action(action)
|
||||
|
||||
def _about(self, action, param):
|
||||
builder = Gtk.Builder().new_from_resource('/org/freedesktop/TuhiGui/AboutDialog.ui')
|
||||
about = builder.get_object('about_dialog')
|
||||
about.set_transient_for(self.get_active_window())
|
||||
about.connect('response', lambda about, param: about.destroy())
|
||||
about.show()
|
||||
|
||||
def _quit(self, action, param):
|
||||
windows = self.get_windows()
|
||||
for window in windows:
|
||||
window.destroy()
|
||||
|
||||
def _help(self, action, param):
|
||||
import time
|
||||
Gtk.show_uri(None, 'https://github.com/tuhiproject/tuhi/wiki', time.time())
|
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
import xdg.BaseDirectory
|
||||
import configparser
|
||||
import logging
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger('config')
|
||||
|
||||
ROOT_PATH = Path(xdg.BaseDirectory.xdg_data_home, 'tuhigui')
|
||||
|
||||
|
||||
class Config(GObject.Object):
|
||||
_config_obj = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.path = Path(ROOT_PATH, 'tuhigui.ini')
|
||||
self.config = configparser.ConfigParser()
|
||||
# Don't lowercase options
|
||||
self.config.optionxform = str
|
||||
self._drawings = []
|
||||
self._load()
|
||||
self._load_cached_drawings()
|
||||
|
||||
def _load(self):
|
||||
if not self.path.exists():
|
||||
return
|
||||
|
||||
logger.debug(f'configuration found')
|
||||
self.config.read(self.path)
|
||||
|
||||
def _load_cached_drawings(self):
|
||||
if not ROOT_PATH.exists():
|
||||
return
|
||||
|
||||
for filename in ROOT_PATH.glob('*.json'):
|
||||
with open(filename) as fd:
|
||||
self._drawings.append(json.load(fd))
|
||||
self.notify('drawings')
|
||||
|
||||
def _write(self):
|
||||
self.path.resolve().parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self.path, 'w') as fd:
|
||||
self.config.write(fd)
|
||||
|
||||
def _add_key(self, section, key, value):
|
||||
if section not in self.config:
|
||||
self.config[section] = {}
|
||||
self.config[section][key] = value
|
||||
self._write()
|
||||
|
||||
@GObject.property
|
||||
def orientation(self):
|
||||
try:
|
||||
return self.config['Device']['Orientation']
|
||||
except KeyError:
|
||||
return 'landscape'
|
||||
|
||||
@orientation.setter
|
||||
def orientation(self, orientation):
|
||||
assert(orientation in ['landscape', 'portrait'])
|
||||
self._add_key('Device', 'Orientation', orientation)
|
||||
|
||||
@GObject.property
|
||||
def drawings(self):
|
||||
return self._drawings
|
||||
|
||||
def add_drawing(self, timestamp, json_string):
|
||||
'''Add a drawing JSON with the given timestamp to the backend
|
||||
storage. This will update self.drawings.'''
|
||||
ROOT_PATH.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
path = Path(ROOT_PATH, f'{timestamp}.json')
|
||||
if path.exists():
|
||||
return
|
||||
|
||||
# Tuhi may still cache files we've 'deleted' locally. These need to
|
||||
# be ignored because they're still technically deleted.
|
||||
deleted = Path(ROOT_PATH, f'{timestamp}.json.deleted')
|
||||
if deleted.exists():
|
||||
return
|
||||
|
||||
with open(path, 'w') as fd:
|
||||
fd.write(json_string)
|
||||
|
||||
self._drawings.append(json.loads(json_string))
|
||||
self.notify('drawings')
|
||||
|
||||
def delete_drawing(self, timestamp):
|
||||
# We don't delete json files immediately, we just rename them
|
||||
# so we can resurrect them in the future if need be.
|
||||
path = Path(ROOT_PATH, f'{timestamp}.json')
|
||||
target = Path(ROOT_PATH, f'{timestamp}.json.deleted')
|
||||
path.rename(target)
|
||||
|
||||
self._drawings = [d for d in self._drawings if d['timestamp'] != timestamp]
|
||||
self.notify('drawings')
|
||||
|
||||
def undelete_drawing(self, timestamp):
|
||||
path = Path(ROOT_PATH, f'{timestamp}.json')
|
||||
target = Path(ROOT_PATH, f'{timestamp}.json.deleted')
|
||||
target.rename(path)
|
||||
|
||||
with open(path) as fd:
|
||||
self._drawings.append(json.load(fd))
|
||||
self.notify('drawings')
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if cls._config_obj is None:
|
||||
cls._config_obj = Config()
|
||||
return cls._config_obj
|
|
@ -0,0 +1,117 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from gettext import gettext as _
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .config import Config
|
||||
from .svg import JsonSvg
|
||||
|
||||
import datetime
|
||||
import time
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
|
||||
def relative_date(timestamp):
|
||||
t = datetime.date.fromtimestamp(timestamp)
|
||||
today = datetime.date.today()
|
||||
diff = t - today
|
||||
|
||||
if diff.days == 0:
|
||||
return _('Today')
|
||||
if diff.days == -1:
|
||||
return _('Yesterday')
|
||||
if diff.days > -4: # last 4 days we convert to weekdays
|
||||
return t.strftime('%A')
|
||||
|
||||
return t.strftime('%x')
|
||||
|
||||
|
||||
@Gtk.Template(resource_path='/org/freedesktop/TuhiGui/ui/Drawing.ui')
|
||||
class Drawing(Gtk.Box):
|
||||
__gtype_name__ = "Drawing"
|
||||
|
||||
label_timestamp = Gtk.Template.Child()
|
||||
image_svg = Gtk.Template.Child()
|
||||
btn_rotate_left = Gtk.Template.Child()
|
||||
btn_rotate_right = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, json_data, *args, **kwargs):
|
||||
super().__init__()
|
||||
self.orientation = Config.instance().orientation
|
||||
|
||||
self.json_data = json_data
|
||||
self.svg = svg = JsonSvg(json_data, orientation=self.orientation)
|
||||
day = relative_date(svg.timestamp)
|
||||
hour = time.strftime('%H:%M', time.localtime(svg.timestamp))
|
||||
|
||||
self.label_timestamp.set_text(f'{day} {hour}')
|
||||
self.image_svg.set_from_file(svg.filename)
|
||||
self.timestamp = svg.timestamp
|
||||
|
||||
def refresh(self):
|
||||
self.svg = svg = JsonSvg(self.json_data, self.orientation)
|
||||
self.image_svg.set_from_file(svg.filename)
|
||||
|
||||
@GObject.Property
|
||||
def name(self):
|
||||
return "drawing"
|
||||
|
||||
@Gtk.Template.Callback('_on_download_button_clicked')
|
||||
def _on_download_button_clicked(self, button):
|
||||
dialog = Gtk.FileChooserDialog(_('Please choose a file'),
|
||||
None,
|
||||
Gtk.FileChooserAction.SAVE,
|
||||
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||
Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
|
||||
|
||||
dialog.set_do_overwrite_confirmation(True)
|
||||
# Translators: the default filename to save to
|
||||
dialog.set_current_name(_('untitled.svg'))
|
||||
|
||||
filter_any = Gtk.FileFilter()
|
||||
# Translators: filter name to show all/any files
|
||||
filter_any.set_name(_('Any files'))
|
||||
filter_any.add_pattern('*')
|
||||
filter_svg = Gtk.FileFilter()
|
||||
# Translators: filter to show svg files only
|
||||
filter_svg.set_name(_('SVG files'))
|
||||
filter_svg.add_pattern('*.svg')
|
||||
dialog.add_filter(filter_svg)
|
||||
dialog.add_filter(filter_any)
|
||||
|
||||
response = dialog.run()
|
||||
if response == Gtk.ResponseType.OK:
|
||||
import shutil
|
||||
file = dialog.get_filename()
|
||||
shutil.copyfile(self.svg.filename, file)
|
||||
# FIXME: error handling
|
||||
|
||||
dialog.destroy()
|
||||
|
||||
@Gtk.Template.Callback('_on_delete_button_clicked')
|
||||
def _on_delete_button_clicked(self, button):
|
||||
Config.instance().delete_drawing(self.timestamp)
|
||||
|
||||
@Gtk.Template.Callback('_on_rotate_button_clicked')
|
||||
def _on_rotate_button_clicked(self, button):
|
||||
if button == self.btn_rotate_left:
|
||||
advance = 1
|
||||
else:
|
||||
advance = 3
|
||||
|
||||
orientations = ['portrait', 'landscape', 'reverse-portrait', 'reverse-landscape'] * 3
|
||||
o = orientations[orientations.index(self.orientation) + advance]
|
||||
self.orientation = o
|
||||
self.refresh()
|
|
@ -0,0 +1,206 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from gettext import gettext as _
|
||||
from gi.repository import GObject, Gtk
|
||||
from .drawing import Drawing
|
||||
from .config import Config
|
||||
|
||||
import time
|
||||
import gi
|
||||
import logging
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger('drawingperspective')
|
||||
|
||||
|
||||
def relative_time(seconds):
|
||||
MIN = 60
|
||||
H = 60 * MIN
|
||||
DAY = 24 * H
|
||||
WEEK = 7 * DAY
|
||||
|
||||
if seconds < 30:
|
||||
return _('just now')
|
||||
if seconds < 5 * MIN:
|
||||
return _('a few minutes ago')
|
||||
if seconds < H:
|
||||
minutes = int(seconds / MIN / 10) * 10
|
||||
return _(f'{minutes} minutes ago')
|
||||
if seconds < DAY:
|
||||
hours = int(seconds / H)
|
||||
return _(f'{hours} hours ago')
|
||||
if seconds < 4 * WEEK:
|
||||
days = int(seconds / DAY)
|
||||
return _(f'{days} days ago')
|
||||
if seconds > 10 * 365 * DAY:
|
||||
return _('not yet')
|
||||
|
||||
return _('a long time ago')
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/org/freedesktop/TuhiGui/ui/DrawingPerspective.ui")
|
||||
class DrawingPerspective(Gtk.Stack):
|
||||
__gtype_name__ = "DrawingPerspective"
|
||||
|
||||
image_battery = Gtk.Template.Child()
|
||||
flowbox_drawings = Gtk.Template.Child()
|
||||
spinner_sync = Gtk.Template.Child()
|
||||
label_last_sync = Gtk.Template.Child()
|
||||
overlay_undo = Gtk.Template.Child()
|
||||
notification_delete_undo = Gtk.Template.Child()
|
||||
notification_delete_close = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.known_drawings = []
|
||||
self.last_sync_time = 0
|
||||
self._sync_label_timer = GObject.timeout_add_seconds(60, self._update_sync_label)
|
||||
self._update_sync_label()
|
||||
Config.instance().connect('notify::orientation', self._on_orientation_changed)
|
||||
|
||||
def _on_orientation_changed(self, config, pspec):
|
||||
# When the orientation changes, we just re-generate all SVGs. This
|
||||
# isn't something that should happen very often anyway so meh.
|
||||
self.flowbox_drawings.foreach(lambda child: child.get_child().refresh())
|
||||
|
||||
def _cache_drawings(self, device, pspec):
|
||||
# The config backend filters duplicates anyway, so don't care here
|
||||
for ts in self.device.drawings_available:
|
||||
json_string = self.device.json(ts)
|
||||
Config.instance().add_drawing(ts, json_string)
|
||||
|
||||
def _update_drawings(self, config, pspec):
|
||||
for js in config.drawings:
|
||||
if js in self.known_drawings:
|
||||
continue
|
||||
|
||||
self.known_drawings.append(js)
|
||||
|
||||
drawing = Drawing(js)
|
||||
|
||||
# We don't know which order we get drawings from the device, so
|
||||
# let's do a sorted insert here
|
||||
index = 0
|
||||
child = self.flowbox_drawings.get_child_at_index(index)
|
||||
while child is not None:
|
||||
if child.get_child().timestamp < drawing.timestamp:
|
||||
break
|
||||
index += 1
|
||||
child = self.flowbox_drawings.get_child_at_index(index)
|
||||
|
||||
self.flowbox_drawings.insert(drawing, index)
|
||||
|
||||
# Remove deleted ones
|
||||
deleted = [d for d in self.known_drawings if d not in config.drawings]
|
||||
for d in deleted:
|
||||
def delete_matching_child(child, drawing):
|
||||
if child.get_child().timestamp == drawing['timestamp']:
|
||||
self.flowbox_drawings.remove(child)
|
||||
self.known_drawings.remove(drawing)
|
||||
self.notification_delete_undo.deleted_drawing = drawing['timestamp']
|
||||
self.overlay_undo.set_reveal_child(True)
|
||||
self.flowbox_drawings.foreach(delete_matching_child, d)
|
||||
|
||||
@GObject.Property
|
||||
def device(self):
|
||||
return self._device
|
||||
|
||||
@device.setter
|
||||
def device(self, device):
|
||||
self._device = device
|
||||
|
||||
device.connect('notify::connected', self._on_connected)
|
||||
device.connect('notify::listening', self._on_listening_stopped)
|
||||
device.connect('notify::sync-state', self._on_sync_state)
|
||||
device.connect('notify::battery-percent', self._on_battery_changed)
|
||||
device.connect('notify::battery-state', self._on_battery_changed)
|
||||
|
||||
# This is a bit convoluted. We need to cache all drawings
|
||||
# because Tuhi doesn't have guaranteed storage. So any json that
|
||||
# comes in from Tuhi, we pass to our config backend to save
|
||||
# somewhere.
|
||||
# The config backend adds the json file and emits a notify for the
|
||||
# json itself (once cached) that we then actually use for SVG
|
||||
# generation.
|
||||
device.connect('notify::drawings-available', self._cache_drawings)
|
||||
Config.instance().connect('notify::drawings', self._update_drawings)
|
||||
|
||||
self._on_battery_changed(device, None)
|
||||
|
||||
self._update_drawings(Config.instance(), None)
|
||||
|
||||
# We always want to sync on startup
|
||||
logger.debug(f'{device.name} - starting to listen')
|
||||
device.start_listening()
|
||||
|
||||
@GObject.Property
|
||||
def name(self):
|
||||
return "drawing_perspective"
|
||||
|
||||
def _on_battery_changed(self, device, pspec):
|
||||
if device.battery_percent > 80:
|
||||
fill = 'full'
|
||||
elif device.battery_percent > 40:
|
||||
fill = 'good'
|
||||
elif device.battery_percent > 10:
|
||||
fill = 'low'
|
||||
else:
|
||||
fill = 'caution'
|
||||
|
||||
if device.battery_state == 1:
|
||||
state = '-charging'
|
||||
elif device.battery_state == 0: # unknown
|
||||
fill = 'missing'
|
||||
state = ''
|
||||
else:
|
||||
state = ''
|
||||
batt_icon_name = f'battery-{fill}{state}-symbolic'
|
||||
_, isize = self.image_battery.get_icon_name()
|
||||
self.image_battery.set_from_icon_name(batt_icon_name, isize)
|
||||
self.image_battery.set_tooltip_text(f'{device.battery_percent}%')
|
||||
|
||||
def _on_sync_state(self, device, pspec):
|
||||
if device.sync_state:
|
||||
self.spinner_sync.start()
|
||||
else:
|
||||
self.spinner_sync.stop()
|
||||
self.last_sync_time = time.time()
|
||||
self._update_sync_label()
|
||||
|
||||
def _update_sync_label(self):
|
||||
now = time.time()
|
||||
self.label_last_sync.set_text(f'{relative_time(now - self.last_sync_time)}')
|
||||
return True
|
||||
|
||||
def _on_connected(self, device, pspec):
|
||||
# Turns out we don't really care about whether the device is
|
||||
# connected or not, it has little effect on how we work here
|
||||
pass
|
||||
|
||||
def _on_listening_stopped(self, device, pspec):
|
||||
if not device.listening:
|
||||
logger.debug(f'{device.name} - listening stopped, restarting')
|
||||
# We never want to stop listening
|
||||
device.start_listening()
|
||||
|
||||
@Gtk.Template.Callback('_on_undo_close_clicked')
|
||||
def _on_undo_close_clicked(self, button):
|
||||
self.overlay_undo.set_reveal_child(False)
|
||||
|
||||
@Gtk.Template.Callback('_on_undo_clicked')
|
||||
def _on_undo_clicked(self, button):
|
||||
Config.instance().undelete_drawing(button.deleted_drawing)
|
||||
self.overlay_undo.set_reveal_child(False)
|
|
@ -0,0 +1,88 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
import xdg.BaseDirectory
|
||||
import svgwrite
|
||||
import os
|
||||
|
||||
DATA_PATH = os.path.join(xdg.BaseDirectory.xdg_data_home, 'tuhigui')
|
||||
|
||||
|
||||
class JsonSvg(GObject.Object):
|
||||
def __init__(self, json, orientation, *args, **kwargs):
|
||||
self.json = json
|
||||
try:
|
||||
os.mkdir(DATA_PATH)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
self.timestamp = json['timestamp']
|
||||
self.filename = os.path.join(DATA_PATH, f'{self.timestamp}.svg')
|
||||
self.orientation = orientation
|
||||
self._convert()
|
||||
|
||||
def _convert(self):
|
||||
js = self.json
|
||||
dimensions = js['dimensions']
|
||||
if dimensions == [0, 0]:
|
||||
width, height = 100, 100
|
||||
else:
|
||||
# Original dimensions are too big for SVG Standard
|
||||
# so we normalize them
|
||||
width, height = dimensions[0] / 100, dimensions[1] / 100
|
||||
|
||||
if self.orientation in ['portrait', 'reverse-Portrait']:
|
||||
size = (height, width)
|
||||
else:
|
||||
size = (width, height)
|
||||
svg = svgwrite.Drawing(filename=self.filename, size=size)
|
||||
g = svgwrite.container.Group(id='layer0')
|
||||
for stroke_num, s in enumerate(js['strokes']):
|
||||
|
||||
points_with_sk_width = []
|
||||
|
||||
for p in s['points']:
|
||||
|
||||
x, y = p['position']
|
||||
# Normalize coordinates too
|
||||
x, y = x / 100, y / 100
|
||||
|
||||
if self.orientation == 'reverse-portrait':
|
||||
x, y = y, width - x
|
||||
elif self.orientation == 'portrait':
|
||||
x, y = height - y, x
|
||||
elif self.orientation == 'reverse-landscape':
|
||||
x, y = width - x, height - y
|
||||
|
||||
delta = (p['pressure'] - 1000.0) / 1000.0
|
||||
stroke_width = 0.4 + 0.20 * delta
|
||||
points_with_sk_width.append((x, y, stroke_width))
|
||||
|
||||
lines = svgwrite.container.Group(id=f'strokes_{stroke_num}', stroke='black')
|
||||
for i, (x, y, stroke_width) in enumerate(points_with_sk_width):
|
||||
if i != 0:
|
||||
xp, yp, stroke_width_p = points_with_sk_width[i - 1]
|
||||
lines.add(
|
||||
svg.line(
|
||||
start=(xp, yp),
|
||||
end=(x, y),
|
||||
stroke_width=stroke_width,
|
||||
style='fill:none'
|
||||
)
|
||||
)
|
||||
g.add(lines)
|
||||
|
||||
svg.add(g)
|
||||
svg.save()
|
|
@ -0,0 +1,397 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gio, GLib
|
||||
import argparse
|
||||
import errno
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
import xdg.BaseDirectory
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger('tuhi')
|
||||
|
||||
CONFIG_PATH = os.path.join(xdg.BaseDirectory.xdg_data_home, 'tuhi-kete')
|
||||
|
||||
TUHI_DBUS_NAME = 'org.freedesktop.tuhi1'
|
||||
ORG_FREEDESKTOP_TUHI1_MANAGER = 'org.freedesktop.tuhi1.Manager'
|
||||
ORG_FREEDESKTOP_TUHI1_DEVICE = 'org.freedesktop.tuhi1.Device'
|
||||
ROOT_PATH = '/org/freedesktop/tuhi1'
|
||||
|
||||
ORG_BLUEZ_DEVICE1 = 'org.bluez.Device1'
|
||||
|
||||
|
||||
class DBusError(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
class _DBusObject(GObject.Object):
|
||||
_connection = None
|
||||
|
||||
def __init__(self, name, interface, objpath):
|
||||
GObject.GObject.__init__(self)
|
||||
|
||||
# this is not handled asynchronously because if we fail to
|
||||
# get the session bus, we have other issues
|
||||
if _DBusObject._connection is None:
|
||||
self._connect_to_session()
|
||||
|
||||
self.interface = interface
|
||||
self.objpath = objpath
|
||||
self._online = False
|
||||
self._name = name
|
||||
try:
|
||||
self._connect()
|
||||
except DBusError:
|
||||
self._reconnect_timer = GObject.timeout_add_seconds(2, self._on_reconnect_timer)
|
||||
|
||||
def _connect(self):
|
||||
try:
|
||||
self.proxy = Gio.DBusProxy.new_sync(self._connection,
|
||||
Gio.DBusProxyFlags.NONE, None,
|
||||
self._name, self.objpath,
|
||||
self.interface, None)
|
||||
if self.proxy.get_name_owner() is None:
|
||||
raise DBusError(f'No-one is handling {self._name}, is the daemon running?')
|
||||
|
||||
self._online = True
|
||||
self.notify('online')
|
||||
except GLib.Error as e:
|
||||
if (e.domain == 'g-io-error-quark' and
|
||||
e.code == Gio.IOErrorEnum.DBUS_ERROR):
|
||||
raise DBusError(e.message)
|
||||
else:
|
||||
raise e
|
||||
|
||||
self.proxy.connect('g-properties-changed', self._on_properties_changed)
|
||||
self.proxy.connect('g-signal', self._on_signal_received)
|
||||
|
||||
def _on_reconnect_timer(self):
|
||||
try:
|
||||
logger.debug('reconnecting')
|
||||
self._connect()
|
||||
return False
|
||||
except DBusError:
|
||||
return True
|
||||
|
||||
@GObject.Property
|
||||
def online(self):
|
||||
return self._online
|
||||
|
||||
def _connect_to_session(self):
|
||||
try:
|
||||
_DBusObject._connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
except GLib.Error as e:
|
||||
if (e.domain == 'g-io-error-quark' and
|
||||
e.code == Gio.IOErrorEnum.DBUS_ERROR):
|
||||
raise DBusError(e.message)
|
||||
else:
|
||||
raise e
|
||||
|
||||
def _on_properties_changed(self, proxy, changed_props, invalidated_props):
|
||||
# Implement this in derived classes to respond to property changes
|
||||
pass
|
||||
|
||||
def _on_signal_received(self, proxy, sender, signal, parameters):
|
||||
# Implement this in derived classes to respond to signals
|
||||
pass
|
||||
|
||||
def property(self, name):
|
||||
p = self.proxy.get_cached_property(name)
|
||||
if p is not None:
|
||||
return p.unpack()
|
||||
return p
|
||||
|
||||
def terminate(self):
|
||||
del(self.proxy)
|
||||
|
||||
|
||||
class _DBusSystemObject(_DBusObject):
|
||||
'''
|
||||
Same as the _DBusObject, but connects to the system bus instead
|
||||
'''
|
||||
def __init__(self, name, interface, objpath):
|
||||
self._connect_to_system()
|
||||
super().__init__(name, interface, objpath)
|
||||
|
||||
def _connect_to_system(self):
|
||||
try:
|
||||
self._connection = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
|
||||
except GLib.Error as e:
|
||||
if (e.domain == 'g-io-error-quark' and
|
||||
e.code == Gio.IOErrorEnum.DBUS_ERROR):
|
||||
raise DBusError(e.message)
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
class BlueZDevice(_DBusSystemObject):
|
||||
def __init__(self, objpath):
|
||||
super().__init__('org.bluez', ORG_BLUEZ_DEVICE1, objpath)
|
||||
self.proxy.connect('g-properties-changed', self._on_properties_changed)
|
||||
|
||||
@GObject.Property
|
||||
def connected(self):
|
||||
return self.proxy.get_cached_property('Connected').unpack()
|
||||
|
||||
def _on_properties_changed(self, obj, properties, invalidated_properties):
|
||||
properties = properties.unpack()
|
||||
|
||||
if 'Connected' in properties:
|
||||
self.notify('connected')
|
||||
|
||||
|
||||
class TuhiKeteDevice(_DBusObject):
|
||||
__gsignals__ = {
|
||||
'button-press-required':
|
||||
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
||||
'registered':
|
||||
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
||||
}
|
||||
|
||||
def __init__(self, manager, objpath):
|
||||
_DBusObject.__init__(self, TUHI_DBUS_NAME,
|
||||
ORG_FREEDESKTOP_TUHI1_DEVICE,
|
||||
objpath)
|
||||
self.manager = manager
|
||||
self.is_registering = False
|
||||
self._bluez_device = BlueZDevice(self.property('BlueZDevice'))
|
||||
self._bluez_device.connect('notify::connected', self._on_connected)
|
||||
self._sync_state = 0
|
||||
|
||||
@classmethod
|
||||
def is_device_address(cls, string):
|
||||
if re.match(r'[0-9a-f]{2}(:[0-9a-f]{2}){5}$', string.lower()):
|
||||
return string
|
||||
raise argparse.ArgumentTypeError(f'"{string}" is not a valid device address')
|
||||
|
||||
@GObject.Property
|
||||
def address(self):
|
||||
return self._bluez_device.property('Address')
|
||||
|
||||
@GObject.Property
|
||||
def name(self):
|
||||
return self._bluez_device.property('Name')
|
||||
|
||||
@GObject.Property
|
||||
def dimensions(self):
|
||||
return self.property('Dimensions')
|
||||
|
||||
@GObject.Property
|
||||
def listening(self):
|
||||
return self.property('Listening')
|
||||
|
||||
@GObject.Property
|
||||
def drawings_available(self):
|
||||
return self.property('DrawingsAvailable')
|
||||
|
||||
@GObject.Property
|
||||
def battery_percent(self):
|
||||
return self.property('BatteryPercent')
|
||||
|
||||
@GObject.Property
|
||||
def battery_state(self):
|
||||
return self.property('BatteryState')
|
||||
|
||||
@GObject.Property
|
||||
def connected(self):
|
||||
return self._bluez_device.connected
|
||||
|
||||
@GObject.Property
|
||||
def sync_state(self):
|
||||
return self._sync_state
|
||||
|
||||
def _on_connected(self, bluez_device, pspec):
|
||||
self.notify('connected')
|
||||
|
||||
def register(self):
|
||||
logger.debug(f'{self}: Register')
|
||||
# FIXME: Register() doesn't return anything useful yet, so we wait until
|
||||
# the device is in the Manager's Devices property
|
||||
self.s1 = self.manager.connect('notify::devices', self._on_mgr_devices_updated)
|
||||
self.is_registering = True
|
||||
self.proxy.Register()
|
||||
|
||||
def start_listening(self):
|
||||
self.proxy.StartListening()
|
||||
|
||||
def stop_listening(self):
|
||||
try:
|
||||
self.proxy.StopListening()
|
||||
except GLib.Error as e:
|
||||
if (e.domain != 'g-dbus-error-quark' or
|
||||
e.code != Gio.IOErrorEnum.EXISTS or
|
||||
Gio.dbus_error_get_remote_error(e) != 'org.freedesktop.DBus.Error.ServiceUnknown'):
|
||||
raise e
|
||||
|
||||
def json(self, timestamp):
|
||||
SUPPORTED_FILE_FORMAT = 1
|
||||
return self.proxy.GetJSONData('(ut)', SUPPORTED_FILE_FORMAT, timestamp)
|
||||
|
||||
def _on_signal_received(self, proxy, sender, signal, parameters):
|
||||
if signal == 'ButtonPressRequired':
|
||||
logger.info(f'{self}: Press button on device now')
|
||||
self.emit('button-press-required', self)
|
||||
elif signal == 'ListeningStopped':
|
||||
err = parameters[0]
|
||||
if err == -errno.EACCES:
|
||||
logger.error(f'{self}: wrong device, please re-register.')
|
||||
elif err < 0:
|
||||
logger.error(f'{self}: an error occured: {os.strerror(-err)}')
|
||||
self.notify('listening')
|
||||
elif signal == 'SyncState':
|
||||
self._sync_state = parameters[0]
|
||||
self.notify('sync-state')
|
||||
|
||||
def _on_properties_changed(self, proxy, changed_props, invalidated_props):
|
||||
if changed_props is None:
|
||||
return
|
||||
|
||||
changed_props = changed_props.unpack()
|
||||
|
||||
if 'DrawingsAvailable' in changed_props:
|
||||
self.notify('drawings-available')
|
||||
elif 'Listening' in changed_props:
|
||||
self.notify('listening')
|
||||
elif 'BatteryPercent' in changed_props:
|
||||
self.notify('battery-percent')
|
||||
elif 'BatteryState' in changed_props:
|
||||
self.notify('battery-state')
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.address} - {self.name}'
|
||||
|
||||
def _on_mgr_devices_updated(self, manager, pspec):
|
||||
if not self.is_registering:
|
||||
return
|
||||
|
||||
for d in manager.devices:
|
||||
if d.address == self.address:
|
||||
self.is_registering = False
|
||||
self.manager.disconnect(self.s1)
|
||||
del(self.s1)
|
||||
logger.info(f'{self}: Registration successful')
|
||||
self.emit('registered', self)
|
||||
|
||||
def terminate(self):
|
||||
try:
|
||||
self.manager.disconnect(self.s1)
|
||||
except AttributeError:
|
||||
pass
|
||||
self._bluez_device.terminate()
|
||||
super(TuhiKeteDevice, self).terminate()
|
||||
|
||||
|
||||
class TuhiKeteManager(_DBusObject):
|
||||
__gsignals__ = {
|
||||
'unregistered-device':
|
||||
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
_DBusObject.__init__(self, TUHI_DBUS_NAME,
|
||||
ORG_FREEDESKTOP_TUHI1_MANAGER,
|
||||
ROOT_PATH)
|
||||
|
||||
self._devices = {}
|
||||
self._unregistered_devices = {}
|
||||
logger.info('starting up')
|
||||
|
||||
if not self.online:
|
||||
self.connect('notify::online', self._init)
|
||||
else:
|
||||
self._init()
|
||||
|
||||
def _init(self, *args, **kwargs):
|
||||
logger.info('manager is online')
|
||||
for objpath in self.property('Devices'):
|
||||
device = TuhiKeteDevice(self, objpath)
|
||||
self._devices[device.address] = device
|
||||
|
||||
@GObject.Property
|
||||
def devices(self):
|
||||
return [v for k, v in self._devices.items()]
|
||||
|
||||
@GObject.Property
|
||||
def unregistered_devices(self):
|
||||
return [v for k, v in self._unregistered_devices.items()]
|
||||
|
||||
@GObject.Property
|
||||
def searching(self):
|
||||
return self.proxy.get_cached_property('Searching')
|
||||
|
||||
def start_search(self):
|
||||
self._unregistered_devices = {}
|
||||
self.proxy.StartSearch()
|
||||
|
||||
def stop_search(self):
|
||||
try:
|
||||
self.proxy.StopSearch()
|
||||
except GLib.Error as e:
|
||||
if (e.domain != 'g-dbus-error-quark' or
|
||||
e.code != Gio.IOErrorEnum.EXISTS or
|
||||
Gio.dbus_error_get_remote_error(e) != 'org.freedesktop.DBus.Error.ServiceUnknown'):
|
||||
raise e
|
||||
self._unregistered_devices = {}
|
||||
|
||||
def terminate(self):
|
||||
for dev in self._devices.values():
|
||||
dev.terminate()
|
||||
self._devices = {}
|
||||
self._unregistered_devices = {}
|
||||
super(TuhiKeteManager, self).terminate()
|
||||
|
||||
def _on_properties_changed(self, proxy, changed_props, invalidated_props):
|
||||
if changed_props is None:
|
||||
return
|
||||
|
||||
changed_props = changed_props.unpack()
|
||||
|
||||
if 'Devices' in changed_props:
|
||||
objpaths = changed_props['Devices']
|
||||
for objpath in objpaths:
|
||||
try:
|
||||
d = self._unregistered_devices[objpath]
|
||||
self._devices[d.address] = d
|
||||
del self._unregistered_devices[objpath]
|
||||
except KeyError:
|
||||
# if we called Register() on an existing device it's not
|
||||
# in unregistered devices
|
||||
pass
|
||||
self.notify('devices')
|
||||
if 'Searching' in changed_props:
|
||||
self.notify('searching')
|
||||
|
||||
def _handle_unregistered_device(self, objpath):
|
||||
for addr, dev in self._devices.items():
|
||||
if dev.objpath == objpath:
|
||||
self.emit('unregistered-device', dev)
|
||||
return
|
||||
|
||||
device = TuhiKeteDevice(self, objpath)
|
||||
self._unregistered_devices[objpath] = device
|
||||
|
||||
logger.debug(f'New unregistered device: {device}')
|
||||
self.emit('unregistered-device', device)
|
||||
|
||||
def _on_signal_received(self, proxy, sender, signal, parameters):
|
||||
if signal == 'SearchStopped':
|
||||
self.notify('searching')
|
||||
elif signal == 'UnregisteredDevice':
|
||||
objpath = parameters[0]
|
||||
self._handle_unregistered_device(objpath)
|
||||
|
||||
def __getitem__(self, btaddr):
|
||||
return self._devices[btaddr]
|
|
@ -0,0 +1,198 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from gettext import gettext as _
|
||||
from gi.repository import Gtk, Gio, GLib, GObject
|
||||
|
||||
from .drawingperspective import DrawingPerspective
|
||||
from .tuhi import TuhiKeteManager
|
||||
from .config import Config
|
||||
|
||||
import logging
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger('window')
|
||||
|
||||
MENU_XML = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<menu id="primary-menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Portrait</attribute>
|
||||
<attribute name="action">win.orientation</attribute>
|
||||
<attribute name="target">portrait</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Landscape</attribute>
|
||||
<attribute name="action">win.orientation</attribute>
|
||||
<attribute name="target">landscape</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Help</attribute>
|
||||
<attribute name="action">app.help</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">About</attribute>
|
||||
<attribute name="action">app.about</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
</interface>
|
||||
"""
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/org/freedesktop/TuhiGui/ui/ErrorPerspective.ui")
|
||||
class ErrorPerspective(Gtk.Box):
|
||||
'''
|
||||
The page loaded when we cannot connect to the Tuhi DBus server.
|
||||
'''
|
||||
__gtype_name__ = "ErrorPerspective"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@GObject.Property
|
||||
def name(self):
|
||||
return "error_perspective"
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/org/freedesktop/TuhiGui/ui/SetupPerspective.ui")
|
||||
class SetupDialog(Gtk.Dialog):
|
||||
'''
|
||||
The setup dialog when we don't yet have a registered device with Tuhi.
|
||||
'''
|
||||
__gtype_name__ = "SetupDialog"
|
||||
__gsignals__ = {
|
||||
'new-device':
|
||||
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
||||
}
|
||||
|
||||
stack = Gtk.Template.Child()
|
||||
label_devicename_p1 = Gtk.Template.Child()
|
||||
btn_quit = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, tuhi, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._tuhi = tuhi
|
||||
self._sig = tuhi.connect('unregistered-device', self._on_unregistered_device)
|
||||
tuhi.start_search()
|
||||
self.device = None
|
||||
|
||||
def _on_unregistered_device(self, tuhi, device):
|
||||
tuhi.disconnect(self._sig)
|
||||
|
||||
self.label_devicename_p1.set_text(_(f'Connecting to {device.name}'))
|
||||
self.stack.set_visible_child_name('page1')
|
||||
self._sig = device.connect('button-press-required', self._on_button_press_required)
|
||||
device.register()
|
||||
|
||||
def _on_button_press_required(self, tuhi, device):
|
||||
tuhi.disconnect(self._sig)
|
||||
|
||||
self.stack.set_visible_child_name('page2')
|
||||
self._sig = device.connect('registered', self._on_registered)
|
||||
|
||||
def _on_registered(self, tuhi, device):
|
||||
tuhi.disconnect(self._sig)
|
||||
self.device = device
|
||||
self.response(Gtk.ResponseType.OK)
|
||||
|
||||
@GObject.Property
|
||||
def name(self):
|
||||
return "setup_dialog"
|
||||
|
||||
|
||||
@Gtk.Template(resource_path='/org/freedesktop/TuhiGui/ui/MainWindow.ui')
|
||||
class MainWindow(Gtk.ApplicationWindow):
|
||||
__gtype_name__ = 'MainWindow'
|
||||
|
||||
stack_perspectives = Gtk.Template.Child()
|
||||
headerbar = Gtk.Template.Child()
|
||||
menubutton1 = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._tuhi = TuhiKeteManager()
|
||||
|
||||
action = Gio.SimpleAction.new_stateful('orientation', GLib.VariantType('s'),
|
||||
GLib.Variant('s', 'landscape'))
|
||||
action.connect('activate', self._on_orientation_changed)
|
||||
action.set_state(GLib.Variant.new_string(Config.instance().orientation))
|
||||
self.add_action(action)
|
||||
|
||||
builder = Gtk.Builder.new_from_string(MENU_XML, -1)
|
||||
menu = builder.get_object("primary-menu")
|
||||
self.menubutton1.set_menu_model(menu)
|
||||
|
||||
ep = ErrorPerspective()
|
||||
self._add_perspective(ep)
|
||||
self.stack_perspectives.set_visible_child_name(ep.name)
|
||||
|
||||
# the dbus bindings need more async...
|
||||
if not self._tuhi.online:
|
||||
self._tuhi.connect('notify::online', self._on_dbus_online)
|
||||
else:
|
||||
self._on_dbus_online()
|
||||
|
||||
def _on_dbus_online(self, *args, **kwargs):
|
||||
logger.debug('dbus is online')
|
||||
|
||||
dp = DrawingPerspective()
|
||||
self._add_perspective(dp)
|
||||
active = dp
|
||||
self.headerbar.set_title(f'Tuhi')
|
||||
self.stack_perspectives.set_visible_child_name(active.name)
|
||||
|
||||
if not self._tuhi.devices:
|
||||
dialog = SetupDialog(self._tuhi)
|
||||
dialog.set_transient_for(self)
|
||||
dialog.connect('response', self._on_setup_dialog_closed)
|
||||
dialog.show()
|
||||
else:
|
||||
dp.device = self._tuhi.devices[0]
|
||||
active = dp
|
||||
self.headerbar.set_title(f'Tuhi - {dp.device.name}')
|
||||
|
||||
def _on_setup_dialog_closed(self, dialog, response):
|
||||
device = dialog.device
|
||||
dialog.destroy()
|
||||
|
||||
if response != Gtk.ResponseType.OK or device is None:
|
||||
self.destroy()
|
||||
return
|
||||
|
||||
logger.debug('device was registered')
|
||||
self.headerbar.set_title(f'Tuhi - {device.name}')
|
||||
|
||||
dp = self._get_child('drawing_perspective')
|
||||
dp.device = device
|
||||
self.stack_perspectives.set_visible_child_name(dp.name)
|
||||
|
||||
def _add_perspective(self, perspective):
|
||||
self.stack_perspectives.add_named(perspective, perspective.name)
|
||||
|
||||
def _get_child(self, name):
|
||||
return self.stack_perspectives.get_child_by_name(name)
|
||||
|
||||
def _on_reconnect_tuhi(self, tuhi):
|
||||
self._tuhi = tuhi
|
||||
|
||||
def _on_orientation_changed(self, action, label):
|
||||
action.set_state(label)
|
||||
Config.instance().orientation = label.get_string() # this is a GVariant
|
Loading…
Reference in New Issue