Compare commits

..

No commits in common. "guimaster" and "v0.13.14" have entirely different histories.

47 changed files with 2278 additions and 5650 deletions

8
.gitignore vendored
View File

@ -1,8 +0,0 @@
*.swp
*.so
*.pb.go
*.patch
go.mod
go.sum
gocui
resources/*.so

View File

@ -1 +0,0 @@
// plugin

674
LICENSE
View File

@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. 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
them 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 prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. 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.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey 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;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If 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 convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU 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 that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
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.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
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.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
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
state 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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program 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, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@ -1,37 +1,11 @@
VERSION = $(shell git describe --tags)
BUILDTIME = $(shell date +%Y.%m.%d)
all: plugin
ldd ../gocui.so
all: clean goimports vet gocui
@ldd gocui.so
plugin:
GO111MODULE=off go build -v -buildmode=plugin -o ../gocui.so
vet:
@GO111MODULE=off go vet
@echo this go plugin builds okay
gocui:
GO111MODULE=off go build -v -x -buildmode=plugin -o gocui.so \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
install:
go build -buildmode=plugin -o ~/go/lib/gocui-${VERSION}.so \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
cd ~/go/lib && ln -f -s gocui-${VERSION}.so gocui.so
# for testing custom golang
custom:
# GO111MODULE=off go build -v
GO111MODULE=off go build -v -work -buildmode=blah
clean:
rm -f gocui *.so go.*
rm -f *.pb.go *.patch
go-mod-clean purge
# Test the README.md & doc.go file
# this runs pkgsite, the binary that does dev.go.dev
# go install golang.org/x/pkgsite/cmd/pkgsite@latest
pkgsite:
pkgsite
goget:
go get -v -t -u
objdump:
objdump -t ../gocui.so |less
@ -40,14 +14,13 @@ log:
reset
tail -f /tmp/witgui.* /tmp/guilogfile
goimports:
goimports -w *.go
cleanbuild:
go build -v -x -buildmode=plugin -o ../nocui.so
check-git-clean:
@git diff-index --quiet HEAD -- || (echo "Git repository is dirty, please commit your changes first"; exit 1)
redomod:
rm -f go.*
GO111MODULE= go mod init
GO111MODULE= go mod tidy
proto:
autogenpb --proto gocuiView.proto
make goimports

View File

@ -1,7 +0,0 @@
# a console toolkit around gocui
* to build, run 'make build'
* TODO: make a way to trigger plugin builds. 'package plugin' maybe?
[terminals that support ture color](https://github.com/termstandard/colors#truecolor-support-in-output-devices)
[more info about color](https://jvns.ca/blog/2024/10/01/terminal-colours/)

80
add.go Normal file
View File

@ -0,0 +1,80 @@
package main
import (
log "go.wit.com/log"
"go.wit.com/widget"
)
var fakeStartWidth int = me.FakeW
var fakeStartHeight int = me.TabH + me.FramePadH
// setup fake labels for non-visible things off screen
func (n *node) setFake() {
w := n.tk
w.isFake = true
n.gocuiSetWH(fakeStartWidth, fakeStartHeight)
fakeStartHeight += w.gocuiSize.Height()
// TODO: use the actual max hight of the terminal window
if fakeStartHeight > 24 {
fakeStartHeight = me.TabH
fakeStartWidth += me.FakeW
}
if true {
n.showView()
}
}
// set the widget start width & height
func (n *node) addWidget() {
nw := n.tk
log.Log(INFO, "setStartWH() w.id =", n.WidgetId, "n.name", n.progname)
switch n.WidgetType {
case widget.Root:
log.Log(INFO, "setStartWH() rootNode w.id =", n.WidgetId, "w.name", n.progname)
nw.color = &colorRoot
n.setFake()
return
case widget.Flag:
nw.color = &colorFlag
n.setFake()
return
case widget.Window:
nw.frame = false
nw.color = &colorWindow
// redoWindows(0,0)
return
case widget.Tab:
nw.color = &colorTab
// redoWindows(0,0)
return
case widget.Button:
nw.color = &colorButton
case widget.Box:
nw.color = &colorBox
nw.isFake = true
n.setFake()
return
case widget.Grid:
nw.color = &colorGrid
nw.isFake = true
n.setFake()
return
case widget.Group:
nw.color = &colorGroup
nw.frame = false
return
case widget.Label:
nw.color = &colorLabel
nw.frame = false
return
default:
/*
if n.IsCurrent() {
n.updateCurrent()
}
*/
}
n.showWidgetPlacement(true, "addWidget()")
}

View File

@ -1,6 +1,3 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
/*
@ -11,9 +8,10 @@ import (
"go.wit.com/log"
)
var outputS []string
var NOW *log.LogFlag
var INFO *log.LogFlag
var GOCUI *log.LogFlag
var SPEW *log.LogFlag
var WARN *log.LogFlag
@ -21,19 +19,14 @@ var WARN *log.LogFlag
var ERROR *log.LogFlag
func init() {
full := "go.wit.com/gui"
full := "go.wit.com/toolkits/gocui"
short := "gocui"
GOCUI = log.NewFlag("GOCUI", true, full, short, "gocui internals")
full = "go.wit.com/toolkits/gocui"
short = "gocui"
NOW = log.NewFlag("NOW", true, full, short, "temp debugging stuff")
INFO = log.NewFlag("INFO", false, full, short, "normal debugging stuff")
WARN = log.NewFlag("WARN", true, full, short, "bad things")
SPEW = log.NewFlag("SPEW", false, full, short, "spew stuff")
ERROR = log.NewFlag("ERROR", true, full, short, "toolkit errors")
ERROR = log.NewFlag("ERROR", false, full, short, "toolkit errors")
}

View File

@ -1,40 +1,33 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
// "github.com/awesome-gocui/gocui"
"go.wit.com/widget"
)
// this comes from the application
func setChecked(n *tree.Node, b bool) {
func (n *node) setCheckbox(b any) {
w := n.tk
if n.WidgetType != widget.Checkbox {
}
n.State.Checked = b
var tk *guiWidget
tk = n.TK.(*guiWidget)
tk.setCheckbox()
}
// redraw the checkbox
func (tk *guiWidget) setCheckbox() {
if tk.WidgetType() != widget.Checkbox {
log.Log(WARN, "setCheckbox() being run on widget:", tk.WidgetType())
return
}
if tk.Checked() {
log.Log(WARN, "setCheckbox() got true", tk.Checked())
tk.labelN = "X " + tk.GetLabel()
if widget.GetBool(b) {
n.value = b
n.tk.label = "X " + n.label
} else {
log.Log(WARN, "setCheckbox() got false", tk.Checked())
tk.labelN = "_ " + tk.GetLabel()
n.value = b
n.tk.label = " " + n.label
}
t := len(n.tk.label) + 1
w.gocuiSize.w1 = w.gocuiSize.w0 + t
tk.Hide()
tk.Show()
// w.realWidth = w.gocuiSize.Width() + me.PadW
// w.realHeight = w.gocuiSize.Height() + me.PadH
// if w.frame {
// w.realWidth += me.FramePadW
// w.realHeight += me.FramePadH
// }
n.deleteView()
n.showView()
}

357
click.go Normal file
View File

@ -0,0 +1,357 @@
package main
import (
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
// set isCurrent = false everywhere
func unsetCurrent(n *node) {
w := n.tk
w.isCurrent = false
if n.WidgetType == widget.Tab {
// n.tk.color = &colorTab
// n.setColor()
}
for _, child := range n.children {
unsetCurrent(child)
}
}
// when adding a new widget, this will update the display
// of the current widgets if that widget is supposed
// to be in current display
func (n *node) updateCurrent() {
log.Log(NOW, "updateCurrent()", n.progname)
if n.WidgetType == widget.Tab {
if n.IsCurrent() {
// n.tk.color = &colorActiveT
n.setColor(&colorActiveT)
n.hideView()
n.showView()
setCurrentTab(n)
} else {
// n.tk.color = &colorTab
// n.setColor()
}
return
}
if n.WidgetType == widget.Window {
if n.IsCurrent() {
// setCurrentWindow(n)
}
return
}
if n.WidgetType == widget.Root {
return
}
n.parent.updateCurrent()
}
// shows the widgets in a window
func setCurrentWindow(n *node) {
if n.IsCurrent() {
return
}
w := n.tk
if n.WidgetType != widget.Window {
return
}
unsetCurrent(me.rootNode)
if n.hasTabs {
// set isCurrent = true on the first tab
for _, child := range n.children {
child.tk.isCurrent = true
break
}
} else {
w.isCurrent = true
}
}
// shows the widgets in a tab
func setCurrentTab(n *node) {
w := n.tk
if n.WidgetType != widget.Tab {
return
}
unsetCurrent(me.rootNode)
w.isCurrent = true
p := n.parent.tk
p.isCurrent = true
log.Log(NOW, "setCurrent()", n.progname)
}
func (n *node) doWidgetClick() {
switch n.WidgetType {
case widget.Root:
// THIS IS THE BEGINING OF THE LAYOUT
log.Log(NOW, "doWidgetClick()", n.progname)
redoWindows(0, 0)
case widget.Flag:
log.Log(NOW, "doWidgetClick() FLAG widget name =", n.progname)
log.Log(NOW, "doWidgetClick() if this is the dropdown menu, handle it here?")
case widget.Window:
if me.currentWindow == n {
return
}
if me.currentWindow != nil {
unsetCurrent(me.currentWindow)
me.currentWindow.setColor(&colorWindow)
me.currentWindow.hideWidgets()
}
n.hideWidgets()
me.currentWindow = n
// setCurrentWindow(n) // probably delete this
n.setColor(&colorActiveW)
n.redoTabs(me.TabW, me.TabH)
for _, child := range n.children {
if child.currentTab == true {
log.Log(NOW, "FOUND CURRENT TAB", child.progname)
setCurrentTab(child)
child.placeWidgets(me.RawW, me.RawH)
child.showWidgets()
return
}
}
/* FIXME: redo this
if ! n.hasTabs {
}
*/
case widget.Tab:
if n.IsCurrent() {
return // do nothing if you reclick on the already selected tab
}
// find the window and disable the active tab
p := n.parent
if p != nil {
p.hideWidgets()
p.redoTabs(me.TabW, me.TabH)
unsetCurrent(p)
for _, child := range p.children {
if child.WidgetType == widget.Tab {
child.setColor(&colorTab)
n.currentTab = false
}
}
}
n.currentTab = true
n.setColor(&colorActiveT)
setCurrentTab(n)
n.placeWidgets(me.RawW, me.RawH)
n.showWidgets()
case widget.Group:
// n.placeWidgets(p.tk.startH, newH)
n.toggleTree()
case widget.Checkbox:
if widget.GetBool(n.value) {
n.setCheckbox(false)
} else {
n.setCheckbox(true)
}
n.doUserEvent()
case widget.Grid:
newR := n.realGocuiSize()
// w,h := n.logicalSize()
// w := newR.w1 - newR.w0
// h := newR.h1 - newR.h0
n.placeGrid(newR.w0, newR.h0)
n.showWidgets()
case widget.Box:
// w.showWidgetPlacement(logNow, "drawTree()")
if n.direction == widget.Horizontal {
log.Log(NOW, "BOX IS HORIZONTAL", n.progname)
} else {
log.Log(NOW, "BOX IS VERTICAL", n.progname)
}
// n.placeWidgets()
n.toggleTree()
case widget.Button:
n.doUserEvent()
case widget.Dropdown:
log.Log(NOW, "do the dropdown here")
if me.ddview == nil {
me.ddview = addDropdown()
tk := me.ddview.tk
tk.gocuiSize.w0 = 20
tk.gocuiSize.w1 = 40
tk.gocuiSize.h0 = 10
tk.gocuiSize.h1 = 25
tk.v, _ = me.baseGui.SetView("ddview",
tk.gocuiSize.w0,
tk.gocuiSize.h0,
tk.gocuiSize.w1,
tk.gocuiSize.h1, 0)
if tk.v == nil {
return
}
tk.v.Wrap = true
tk.v.Frame = true
tk.v.Clear()
fmt.Fprint(tk.v, "example.com\nwit.com")
me.ddview.SetVisible(true)
return
}
log.Log(NOW, "doWidgetClick() visible =", me.ddview.Visible())
if me.ddview.Visible() {
me.ddview.SetVisible(false)
me.baseGui.DeleteView("ddview")
me.ddview.tk.v = nil
} else {
var dnsList string
for i, s := range n.vals {
log.Log(NOW, "AddText()", n.progname, i, s)
dnsList += s + "\n"
}
me.ddNode = n
log.Log(NOW, "new dns list should be set to:", dnsList)
me.ddview.label = dnsList
me.ddview.SetText(dnsList)
me.ddview.SetVisible(true)
}
for i, s := range n.vals {
log.Log(NOW, "AddText()", n.progname, i, s)
}
default:
}
}
var toggle bool = true
func (n *node) toggleTree() {
if toggle {
n.drawTree(toggle)
toggle = false
} else {
n.hideWidgets()
toggle = true
}
}
// display the widgets in the binary tree
func (n *node) drawTree(draw bool) {
w := n.tk
if w == nil {
return
}
n.showWidgetPlacement(true, "drawTree()")
if draw {
// w.textResize()
n.showView()
} else {
n.deleteView()
}
for _, child := range n.children {
child.drawTree(draw)
}
}
func click(g *gocui.Gui, v *gocui.View) error {
// var l string
// var err error
log.Log(INFO, "click() START", v.Name())
// n := me.rootNode.findWidgetName(v.Name())
n := findUnderMouse()
if n != nil {
log.Log(NOW, "click() Found widget =", n.WidgetId, n.progname, ",", n.label)
if n.progname == "DropBox" {
log.Log(NOW, "click() this is the dropdown menu. set a flag here what did I click? where is the mouse?")
log.Log(NOW, "click() set a global dropdown clicked flag=true here")
me.ddClicked = true
}
n.doWidgetClick()
} else {
log.Log(NOW, "click() could not find node name =", v.Name())
}
if _, err := g.SetCurrentView(v.Name()); err != nil {
log.Log(NOW, "click() END err =", err)
return err
}
log.Log(NOW, "click() END")
return nil
}
func findUnderMouse() *node {
var found *node
var widgets []*node
var f func(n *node)
w, h := me.baseGui.MousePosition()
// find buttons that are below where the mouse button click
f = func(n *node) {
widget := n.tk
// ignore widgets that are not visible
if n.Visible() {
if (widget.gocuiSize.w0 <= w) && (w <= widget.gocuiSize.w1) &&
(widget.gocuiSize.h0 <= h) && (h <= widget.gocuiSize.h1) {
widgets = append(widgets, n)
found = n
}
}
if n == me.ddview {
log.Log(NOW, "findUnderMouse() found ddview")
if n.Visible() {
log.Log(NOW, "findUnderMouse() and ddview is visable. hide it here. TODO: find highlighted row")
found = n
// find the actual value here and set the dropdown widget
me.baseGui.DeleteView("ddview")
} else {
log.Log(NOW, "findUnderMouse() I was lying, actually it's not found")
}
}
for _, child := range n.children {
f(child)
}
}
f(me.rootNode)
// widgets has everything that matches
// TODO: pop up menu with a list of them
for _, n := range widgets {
//log(logNow, "ctrlDown() FOUND widget", widget.id, widget.name)
n.showWidgetPlacement(true, "findUnderMouse() FOUND")
}
return found
}
// find the widget under the mouse click
func ctrlDown(g *gocui.Gui, v *gocui.View) error {
var found *node
// var widgets []*node
// var f func (n *node)
found = findUnderMouse()
if me.ctrlDown == nil {
setupCtrlDownWidget()
me.ctrlDown.label = found.progname
me.ctrlDown.tk.cuiName = "ctrlDown"
// me.ctrlDown.parent = me.rootNode
}
cd := me.ctrlDown.tk
if found == nil {
found = me.rootNode
}
me.ctrlDown.label = found.progname
newR := found.realGocuiSize()
cd.gocuiSize.w0 = newR.w0
cd.gocuiSize.h0 = newR.h0
cd.gocuiSize.w1 = newR.w1
cd.gocuiSize.h1 = newR.h1
if me.ctrlDown.Visible() {
me.ctrlDown.hideView()
} else {
me.ctrlDown.showView()
}
me.ctrlDown.showWidgetPlacement(true, "ctrlDown:")
return nil
}

438
color.go
View File

@ -1,360 +1,26 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"github.com/awesome-gocui/gocui"
"math/rand"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
// simple colors for light and dark
//w.v.SelBgColor = gocui.ColorCyan
//color.go: w.v.SelFgColor = gocui.ColorBlack
//color.go: w.v.BgColor = gocui.ColorGreen
// information about how terminfo works
// https://jvns.ca/blog/2024/10/01/terminal-colours/
// TODO: move all this to a protobuf
// TODO: add black/white only flag for ttyS0
// TODO: fix kvm/qemu serial console & SIGWINCH.
// TODO: check minicom (doesn't work)
// TODO: fix riscv boards
// DONE ON ENABLE() WIDGET
// restores the last saved color and makes it active
func (tk *guiWidget) restoreEnableColor() {
if tk.color == nil {
tk.color = new(colorT)
}
tk.color.frame = tk.colorLast.frame
tk.color.fg = tk.colorLast.fg
tk.color.bg = tk.colorLast.bg
tk.color.selFg = tk.colorLast.selFg
tk.color.selBg = tk.colorLast.selBg
tk.activateColor()
}
// DONE ON DISABLE() WIDGET
// makes the button look disabled
func (tk *guiWidget) setColorDisable() {
if tk.color == nil {
tk.color = new(colorT)
}
// save the current color
tk.color.frame = superLightGrey
tk.color.fg = gocui.ColorBlack
tk.color.bg = superLightGrey
tk.color.selFg = superLightGrey
tk.color.selBg = superLightGrey
tk.activateColor()
}
// sets the current gocui highlight colors
func (tk *guiWidget) activateColor() {
if tk.v == nil {
return
}
tk.v.FrameColor = tk.color.frame
tk.v.FgColor = tk.color.fg
tk.v.BgColor = tk.color.bg
tk.v.SelFgColor = tk.color.selFg
tk.v.SelBgColor = tk.color.selBg
}
// saves the color and makes it active
func (tk *guiWidget) updateColor() {
if tk.v == nil {
return
}
if tk.color != nil {
tk.colorLast.frame = tk.color.frame
tk.colorLast.fg = tk.color.fg
tk.colorLast.bg = tk.color.bg
tk.colorLast.selFg = tk.color.selFg
tk.colorLast.selBg = tk.color.selBg
}
tk.activateColor()
}
// Below are all the colors. TODO: move to protobuf and save in a config file
func (tk *guiWidget) setColorWindowFrame() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark { // use a dark color palette
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
}
tk.updateColor()
}
// weird. lots of color problems for me on debian sid using the traditional Andy Herzfield 'gnome'
func (tk *guiWidget) setColorWindowTitleActive() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark { // use a dark color palette
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.ColorWhite
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlue
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
}
tk.updateColor()
}
func (tk *guiWidget) setColorWindowTitle() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark { // use a dark color palette
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.ColorWhite
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
}
tk.updateColor()
}
func (tk *guiWidget) setColorBG() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.ColorWhite
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
}
tk.updateColor()
}
func (tk *guiWidget) setColorLabel() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorWhite
}
tk.updateColor()
}
func (tk *guiWidget) setColorLabelTable() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorGreen
}
tk.updateColor()
}
func (tk *guiWidget) setColorButtonDense() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlue
tk.color.selFg = gocui.ColorBlue
tk.color.selBg = gocui.AttrNone
}
tk.updateColor()
}
func (tk *guiWidget) setColorNotifyIcon() {
if tk.color == nil {
tk.color = new(colorT)
}
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlue
tk.color.selFg = gocui.ColorBlue
tk.color.selBg = gocui.AttrNone
tk.updateColor()
}
func (tk *guiWidget) setColorButton() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorBlack
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.ColorBlue
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
}
tk.updateColor()
}
func (tk *guiWidget) setColorInput() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorYellow
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorYellow
tk.color.selBg = gocui.ColorBlack
} else {
tk.color.frame = gocui.ColorYellow
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorYellow
tk.color.selBg = gocui.ColorBlack
}
tk.updateColor()
}
func (tk *guiWidget) setColorModal() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.ColorRed
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorBlack
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorWhite
}
tk.updateColor()
}
// what genius figured this out?
func (tk *guiWidget) setColorTextbox() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.ColorRed
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorBlack
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorWhite
}
tk.updateColor()
}
// just notes down here
// what genius figured this out?
// originally from github.com/dimasma0305/GoFetch
func get_teminal_color_palette() string {
// var runes rune
// color1 := "\x1b[0;29m  \x1b[0m"
// runes = []rune(color1)
// view.WriteRunes(runes)
color1 := "\x1b[0;29m  \x1b[0m"
color2 := "\x1b[0;31m  \x1b[0m"
color3 := "\x1b[0;32m  \x1b[0m"
color4 := "\x1b[0;33m  \x1b[0m"
color5 := "\x1b[0;34m  \x1b[0m"
color6 := "\x1b[0;35m  \x1b[0m"
color7 := "\x1b[0;36m  \x1b[0m"
color8 := "\x1b[0;37m  \x1b[0m"
return color1 + " " + color2 + " " + color3 + " " + color4 + " " + color5 + " " + color6 + " " + color7 + " " + color8
}
func randColor() gocui.Attribute {
colors := []string{"Green", "#FFAA55", "Yellow", "Blue", "Red", "Black", "White"}
i := rand.Intn(len(colors))
log.Log(NOW, "randColor() i =", i)
return gocui.GetColor(colors[i])
type colorT struct {
frame gocui.Attribute
fg gocui.Attribute
bg gocui.Attribute
selFg gocui.Attribute
selBg gocui.Attribute
name string
}
var none gocui.Attribute = gocui.AttrNone
var colorNone colorT = colorT{none, none, none, none, none, "debug none"}
var lightPurple gocui.Attribute = gocui.GetColor("#DDDDDD") // light purple
var darkPurple gocui.Attribute = gocui.GetColor("#FFAA55") // Dark Purple
var heavyPurple gocui.Attribute = gocui.GetColor("#88AA55") // heavy purple
@ -370,3 +36,85 @@ var superLightGrey gocui.Attribute = gocui.GetColor("#55AAFF") // super light gr
// v.BgColor = gocui.GetColor("#55AAFF") // super light grey
// v.BgColor = gocui.GetColor("#FFC0CB") // 'w3c pink' yellow
// Normal Text On mouseover
//
// Widget Frame Text background Text background
var colorWindow colorT = colorT{none, gocui.ColorBlue, none, none, powdererBlue, "normal window"}
var colorActiveW colorT = colorT{none, none, powdererBlue, none, powdererBlue, "active window"}
var colorTab colorT = colorT{gocui.ColorBlue, gocui.ColorBlue, none, none, powdererBlue, "normal tab"}
var colorActiveT colorT = colorT{gocui.ColorBlue, none, powdererBlue, none, powdererBlue, "active tab"}
var colorButton colorT = colorT{gocui.ColorGreen, none, gocui.ColorWhite, gocui.ColorGreen, gocui.ColorBlack, "normal button"}
var colorLabel colorT = colorT{none, none, superLightGrey, none, superLightGrey, "normal label"}
var colorGroup colorT = colorT{none, none, superLightGrey, none, superLightGrey, "normal group"}
// widget debugging colors. these widgets aren't displayed unless you are debugging
var colorRoot colorT = colorT{gocui.ColorRed, none, powdererBlue, none, gocui.ColorBlue, "debug root"}
var colorFlag colorT = colorT{gocui.ColorRed, none, powdererBlue, none, gocui.ColorGreen, "debug flag"}
var colorBox colorT = colorT{gocui.ColorRed, none, lightPurple, none, gocui.ColorCyan, "debug box"}
var colorGrid colorT = colorT{gocui.ColorRed, none, lightPurple, none, gocui.ColorRed, "debug grid"}
var colorNone colorT = colorT{none, none, none, none, none, "debug none"}
// actually sets the colors for the gocui element
// the user will see the colors change when this runs
// TODO: add black/white only flag for ttyS0
// TODO: or fix kvm/qemu serial console & SIGWINCH.
// TODO: and minicom and uboot and 5 million other things.
// TODO: maybe enough of us could actually do that if we made it a goal.
// TODO: start with riscv boards and fix it universally there
// TODO: so just a small little 'todo' item here
func (n *node) setColor(newColor *colorT) {
tk := n.tk
if tk.color == newColor {
// nothing to do since the colors have nto changed
return
}
tk.color = newColor
if tk.v == nil {
return
}
if tk.color == nil {
log.Log(NOW, "Set the node to color = nil")
tk.color = &colorNone
}
log.Log(NOW, "Set the node to color =", tk.color.name)
n.recreateView()
}
func (n *node) setDefaultWidgetColor() {
n.showView()
}
func (n *node) setDefaultHighlight() {
w := n.tk
if w.v == nil {
log.Log(ERROR, "SetColor() failed on view == nil")
return
}
w.v.SelBgColor = gocui.ColorGreen
w.v.SelFgColor = gocui.ColorBlack
}
func randColor() gocui.Attribute {
colors := []string{"Green", "#FFAA55", "Yellow", "Blue", "Red", "Black", "White"}
i := rand.Intn(len(colors))
log.Log(NOW, "randColor() i =", i)
return gocui.GetColor(colors[i])
}
func (n *node) redoColor(draw bool) {
w := n.tk
if w == nil {
return
}
log.Sleep(.05)
n.setDefaultHighlight()
n.setDefaultWidgetColor()
for _, child := range n.children {
child.redoColor(draw)
}
}

View File

@ -1,7 +0,0 @@
#!/usr/bin/python3
def color(num, text):
return f"\033[38;5;{num}m{text}\033[0m"
for i in range(300):
print(color(i, f"number {i:02}"))

218
common.go Normal file
View File

@ -0,0 +1,218 @@
package main
/*
These code should be common to all gui plugins
There are some helper functions that are probably going to be
the same everywhere. Mostly due to handling the binary tree structure
and the channel communication
For now, it's just a symlink to the 'master' version in
./toolkit/nocui/common.go
*/
import (
"go.wit.com/log"
"go.wit.com/widget"
)
// this is the channel we send user events like
// mouse clicks or keyboard events back to the program
var callback chan widget.Action
// this is the channel we get requests to make widgets
var pluginChan chan widget.Action
type node struct {
parent *node
children []*node
WidgetId int // widget ID
WidgetType widget.WidgetType
ParentId int // parent ID
state widget.State
// a reference name for programming and debuggign. Must be unique
progname string
// the text used for button labesl, window titles, checkbox names, etc
label string
// horizontal means layout widgets like books on a bookshelf
// vertical means layout widgets like books in a stack
// direction widget.Orientation
direction widget.Orientation
// This is how the values are passed back and forth
// values from things like checkboxes & dropdown's
value any
strings []string
// This is used for things like a slider(0,100)
X int
Y int
// This is for the grid size & widget position
W int
H int
AtW int
AtH int
vals []string // dropdown menu items
// horizontal bool `default:false`
hasTabs bool // does the window have tabs?
currentTab bool // the visible tab
// the internal plugin toolkit structure
// in the gtk plugin, it has gtk things like margin & border settings
// in the text console one, it has text console things like colors for menus & buttons
tk *guiWidget
}
// searches the binary tree for a WidgetId
func (n *node) findWidgetId(id int) *node {
if n == nil {
return nil
}
if n.WidgetId == id {
return n
}
for _, child := range n.children {
newN := child.findWidgetId(id)
if newN != nil {
return newN
}
}
return nil
}
func (n *node) doUserEvent() {
if callback == nil {
log.Log(ERROR, "doUserEvent() callback == nil", n.WidgetId)
return
}
var a widget.Action
a.WidgetId = n.WidgetId
a.Value = n.value
a.ActionType = widget.User
log.Log(INFO, "doUserEvent() START: send a user event to the callback channel")
callback <- a
log.Log(INFO, "doUserEvent() END: sent a user event to the callback channel")
return
}
// Other goroutines must use this to access the GUI
//
// You can not acess / process the GUI thread directly from
// other goroutines. This is due to the nature of how
// Linux, MacOS and Windows work (they all work differently. suprise. surprise.)
//
// this sets the channel to send user events back from the plugin
func Callback(guiCallback chan widget.Action) {
callback = guiCallback
}
func PluginChannel() chan widget.Action {
return pluginChan
}
/*
func convertString(val any) string {
switch v := val.(type) {
case bool:
n.B = val.(bool)
case string:
n.label = val.(string)
n.S = val.(string)
case int:
n.I = val.(int)
default:
log.Error(errors.New("Set() unknown type"), "v =", v)
}
}
*/
/*
// this is in common.go, do not move it
func getString(A any) string {
if A == nil {
log.Warn("getString() got nil")
return ""
}
var k reflect.Kind
k = reflect.TypeOf(A).Kind()
switch k {
case reflect.Int:
var i int
i = A.(int)
return string(i)
case reflect.String:
return A.(string)
case reflect.Bool:
if A.(bool) == true {
return "true"
} else {
return "false"
}
default:
log.Warn("getString uknown kind", k, "value =", A)
return ""
}
return ""
}
*/
// this is in common.go, do not move it
func addNode(a *widget.Action) *node {
n := new(node)
n.WidgetType = a.WidgetType
n.WidgetId = a.WidgetId
n.ParentId = a.ParentId
n.state = a.State
// copy the data from the action message
n.progname = a.ProgName
n.value = a.Value
n.direction = a.Direction
n.strings = a.Strings
// TODO: these need to be rethought
n.X = a.X
n.Y = a.Y
n.W = a.W
n.H = a.H
n.AtW = a.AtW
n.AtH = a.AtH
// store the internal toolkit information
n.tk = initWidget(n)
// n.tk = new(guiWidget)
if a.WidgetType == widget.Root {
log.Log(INFO, "addNode() Root")
return n
}
if me.rootNode.findWidgetId(a.WidgetId) != nil {
log.Log(ERROR, "addNode() WidgetId already exists", a.WidgetId)
return me.rootNode.findWidgetId(a.WidgetId)
}
// add this new widget on the binary tree
n.parent = me.rootNode.findWidgetId(a.ParentId)
if n.parent != nil {
n.parent.children = append(n.parent.children, n)
//w := n.tk
//w.parent = n.parent.tk
//w.parent.children = append(w.parent.children, w)
}
return n
}

116
debug.go
View File

@ -1,106 +1,74 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
func (w *guiWidget) dumpTree(s string) {
// log.Log(ERROR, "dump w", w.WidgetId(), w.WidgetType, w.String())
func (n *node) dumpTree(draw bool) {
w := n.tk
if w == nil {
log.Log(ERROR, "dump w.TK == nil", w.WidgetId(), w.WidgetType(), w.String())
return
}
w.dumpWidget("dumpTree() " + s)
n.showWidgetPlacement(true, "dumpTree()")
for _, child := range w.children {
child.dumpTree(s)
for _, child := range n.children {
child.dumpTree(draw)
}
}
func (w *guiWidget) dumpWindows(s string) {
// log.Log(ERROR, "dump w", w.WidgetId(), w.WidgetType, w.String())
if w == nil {
log.Log(ERROR, "dump w.TK == nil", w.WidgetId(), w.WidgetType(), w.String())
func (n *node) showWidgetPlacement(b bool, s string) {
if n == nil {
log.Log(ERROR, "WTF w == nil")
return
}
if w.WidgetType() == widget.Window {
s += fmt.Sprintf(" F(%d,%d)", w.force.w0, w.force.h0)
// can't set this here. doesn't work
// w.full.w0 = w.force.w0
// w.full.h0 = w.force.h0
w.dumpWidget("dumpWindow() " + s)
w.windowFrame.dumpWidget("dumpFrame() " + s)
}
w := n.tk
for _, child := range w.children {
child.dumpWindows(s)
}
}
// a standard function to print out information about a widget
func (tk *guiWidget) dumpWidget(s string) {
var s1 string
var pId int
// tk.verifyRect()
if tk.parent == nil {
log.Logf(WARN, "showWidgetPlacement() parent == nil wId=%d cuiName=%s", tk.WidgetId(), tk.cuiName)
if n.parent == nil {
log.Log(INFO, "showWidgetPlacement() parent == nil", n.WidgetId, w.cuiName)
pId = 0
} else {
pId = tk.parent.WidgetId()
pId = n.parent.WidgetId
}
s1 = fmt.Sprintf("(wId,pId)=(%4d,%4d) ", tk.WidgetId(), pId)
sizeW, sizeH := tk.Size()
hide := "S"
if tk.Hidden() {
hide = "H"
}
s1 += fmt.Sprintf("size=(%3d,%3d)%s)", sizeW, sizeH, hide)
if tk.Visible() {
s1 += fmt.Sprintf("gocui=(%3d,%3d,%3d,%3d)",
tk.gocuiSize.w0, tk.gocuiSize.h0, tk.gocuiSize.w1, tk.gocuiSize.h1)
s1 = fmt.Sprintf("(wId,pId)=(%2d,%2d) ", n.WidgetId, pId)
if n.Visible() {
s1 += fmt.Sprintf("gocui=(%2d,%2d)(%2d,%2d,%2d,%2d)",
w.gocuiSize.Width(), w.gocuiSize.Height(),
w.gocuiSize.w0, w.gocuiSize.h0, w.gocuiSize.w1, w.gocuiSize.h1)
} else {
s1 += fmt.Sprintf(" %3s %3s %3s %3s ", "", "", "", "")
s1 += fmt.Sprintf(" ")
}
s1 += fmt.Sprintf(" full=(%3d,%3d,%3d,%3d)", tk.full.w0, tk.full.h0, tk.full.w1, tk.full.h1)
if tk.parent != nil {
if tk.parent.WidgetType() == widget.Grid {
s1 += fmt.Sprintf("At(%3d,%3d)", tk.GridW(), tk.GridH())
s1 += fmt.Sprintf("(%3d,%3d) ", tk.parent.widths[tk.GridW()], tk.parent.heights[tk.GridH()])
} else {
s1 += fmt.Sprintf(" %3s %3s ", "", "")
s1 += fmt.Sprintf(" %3s %3s ", "", "")
if n.parent != nil {
if n.parent.WidgetType == widget.Grid {
s1 += fmt.Sprintf("At(%2d,%2d) ", n.AtW, n.AtH)
}
} else {
s1 += fmt.Sprintf(" %3s %3s ", "", "")
}
var end string
if tk.WidgetType() == widget.Box {
end = fmt.Sprintf("%-8s %-8s %s %s", tk.WidgetType(), tk.cuiName, tk.Direction().String(), tk.String())
} else {
end = fmt.Sprintf("%-8s %-8s %s", tk.WidgetType(), tk.cuiName, tk.String())
}
if tk.node.InTable() {
// log.Log(GOCUI, "findParentTAble()", tk.labelN, tk.cuiName, tk.node.WidgetId)
end += " (table)"
}
log.Log(GOCUI, s1, s, end)
tmp := "." + n.progname + "."
log.Log(INFO, s1, s, n.WidgetType, ",", tmp) // , "text=", w.text)
}
func printWidgetTree(g *gocui.Gui, v *gocui.View) error {
me.treeRoot.ListWidgets()
return nil
func (n *node) dumpWidget(pad string) {
log.Log(NOW, "node:", pad, n.WidgetId, "At(", n.AtW, n.AtH, ") ,", n.WidgetType, ", n.progname =", n.progname, ", n.label =", n.label)
}
func printWidgetPlacements(g *gocui.Gui, v *gocui.View) error {
w := me.treeRoot.TK.(*guiWidget)
w.dumpTree("MM")
w.dumpWindows("WW")
return nil
func (n *node) listWidgets() {
if n == nil {
return
}
var pad string
for i := 0; i < me.depth; i++ {
pad = pad + " "
}
n.dumpWidget(pad)
for _, child := range n.children {
me.depth += 1
child.listWidgets()
me.depth -= 1
}
return
}

View File

@ -1,115 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// simulates a dropdown menu in gocui
import (
"fmt"
"strings"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
// create a new widget in the binary tree
func makeNewFlagWidget(wId int) *guiWidget {
n := new(tree.Node)
n.WidgetType = widget.Flag
n.WidgetId = wId
n.ParentId = 0
// store the internal toolkit information
tk := new(guiWidget)
tk.frame = true
tk.node = n
if tk.node.Parent == nil {
tk.node.Parent = me.treeRoot
}
// set the name used by gocui to the id
tk.cuiName = fmt.Sprintf("%d DR", wId)
tk.setColorInput()
// add this new widget on the binary tree
tk.parent = me.treeRoot.TK.(*guiWidget)
if tk.parent == nil {
panic("makeNewFlagWidget() didn't get treeRoot guiWidget")
} else {
tk.parent.children = append(tk.parent.children, tk)
}
n.TK = tk
return tk
}
func (tk *guiWidget) showDropdown() {
if me.dropdown.tk == nil {
// should only happen once
me.dropdown.tk = makeNewFlagWidget(me.dropdown.wId)
me.dropdown.tk.dumpWidget("init() dropdown")
}
if me.dropdown.tk == nil {
log.Log(GOCUI, "showDropdown() Is Broken!")
return
}
// todo: fix this after switching to protobuf
me.dropdown.items = []string{} // zero out whatever was there before
for i, s := range tk.node.Strings() {
log.Log(GOCUI, "showDropdown()", tk.String(), i, s)
me.dropdown.items = append(me.dropdown.items, s)
}
log.Log(GOCUI, "new dropdown items should be set to:", me.dropdown.items)
startW, startH := tk.Position()
log.Log(GOCUI, "showDropdown() SHOWING AT W,H=", startW, startH)
me.dropdown.tk.Hide()
me.dropdown.tk.MoveToOffset(startW+3, startH+2)
me.dropdown.tk.labelN = strings.Join(me.dropdown.items, "\n")
me.dropdown.tk.Show()
me.dropdown.active = true
me.dropdown.callerTK = tk
r := me.dropdown.tk.gocuiSize // set the 'full' size so that mouse clicks are sent here
me.dropdown.tk.full.w0 = r.w0
me.dropdown.tk.full.w1 = r.w1
me.dropdown.tk.full.h0 = r.h0
me.dropdown.tk.full.h1 = r.h1
me.dropdown.tk.dumpWidget("showDropdown()")
}
// if there is a drop down view active, treat it like a dialog box and close it
func (w *guiWidget) dropdownClicked(mouseW, mouseH int) string {
w.Hide()
me.dropdown.active = false
// only need height to figure out what line in the dropdown menu the user clicked
_, startH := w.Position()
itemNumber := mouseH - startH
items := me.dropdown.items
// log.Log(GOCUI, "dropdownClicked() look for item", itemNumber, "len(items) =", len(items))
if itemNumber < 1 {
return ""
}
if len(items) >= itemNumber {
// log.Log(GOCUI, "dropdownClicked() found", items[itemNumber-1])
if items[itemNumber-1] != "" {
if me.dropdown.tk != nil {
// log.Log(GOCUI, "dropdownClicked() send event for", me.dropdownW.cuiName, me.dropdownW.node.WidgetType)
me.dropdown.callerTK.SetText(items[itemNumber-1])
me.dropdown.callerTK.node.SetCurrentS(items[itemNumber-1])
me.myTree.SendUserEvent(me.dropdown.callerTK.node)
}
}
return items[itemNumber-1]
}
return ""
}

View File

@ -1,242 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"syscall"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
// register how the 'gocui' will work as a GO toolkit plugin
// all applications will use these keys. they are universal.
// tells 'gocui' where to send events
func registerHandlers(g *gocui.Gui) {
defer func() {
if r := recover(); r != nil {
log.Info("EVENT BINDINGS recovered in r", r)
return
}
}()
// mouse handlers
g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, mouseDown) // normal left mouse down
g.SetKeybinding("", gocui.MouseLeft, gocui.ModMouseCtrl, ctrlDown) // mouse with the ctrl key held down
g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, mouseUp) // mouse button release
g.SetKeybinding("", gocui.MouseWheelUp, gocui.ModNone, wheelsUp) // mouse button release
g.SetKeybinding("", gocui.MouseWheelDown, gocui.ModNone, wheelsDown) // mouse button release
// Ctrl key handlers
g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, doExit) // CTRL-C : exits the application
g.SetKeybinding("", gocui.KeyCtrlV, gocui.ModNone, doPanic) // CTRL-V : force a panic()
g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, openDebuggger) // CTRL-D : open the (D)ebugger
keyForced, modForced := gocui.MustParse("ctrl+z") // setup ctrl+z
g.SetKeybinding("", keyForced, modForced, handle_ctrl_z) // CTRL-Z :cleverly let's you background gocui (breaks cursor mouse on return)
g.SetKeybinding("", gocui.KeyEsc, gocui.ModNone, doEsc) // escape key
// regular keys
g.SetKeybinding("", 'H', gocui.ModNone, theHelp) // 'H' toggles on and off the help menu
g.SetKeybinding("", 'O', gocui.ModNone, theStdout) // 'O' toggle the STDOUT window
g.SetKeybinding("", 'D', gocui.ModNone, theDarkness) // 'D' toggles light/dark mode
g.SetKeybinding("", 'q', gocui.ModNone, doExit) // 'q' exit
g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, tabCycleWindows) // '2' use this to test new ideas
// stdout keys
g.SetKeybinding("", gocui.KeyPgup, gocui.ModNone, stdoutPgup) // Pgup scroll up the Stdout buffer
g.SetKeybinding("", gocui.KeyPgdn, gocui.ModNone, stdoutPgdn) // Pgdn scroll down the Stdout buffer
g.SetKeybinding("", gocui.KeyHome, gocui.ModNone, stdoutHome) // Pgdn scroll down the Stdout buffer
g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, stdoutArrowUp) // Pgdn scroll down the Stdout buffer
g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, stdoutArrowDown) // Pgdn scroll down the Stdout buffer
// debugging
g.SetKeybinding("", '2', gocui.ModNone, theNotsure) // '2' use this to test new ideas
g.SetKeybinding("", 'S', gocui.ModNone, theSuperMouse) // 'S' Super Mouse mode!
g.SetKeybinding("", 'M', gocui.ModNone, printWidgetPlacements) // 'M' list all widgets with positions
g.SetKeybinding("", 'L', gocui.ModNone, printWidgetTree) // 'L' list all widgets in tree view
g.SetKeybinding("", 'f', gocui.ModNone, theFind) // 'f' shows what is under your mouse
g.SetKeybinding("", 'd', gocui.ModNone, theLetterD) // 'd' toggles on and off debugging buttons
g.SetKeybinding("", 'r', gocui.ModNone, reverseStdout) // 'r' turns scrolling of STDOUT upside down
g.SetKeybinding("", 'q', gocui.ModNone, quit) // 'q' only exits gocui. plugin stays alive (?)
}
// flips on 'super mouse' mode // this was awesome for debugging gocui. never remove this code.
// while this is turned on, it will print out every widget found under the mouse
func theSuperMouse(g *gocui.Gui, v *gocui.View) error {
if me.supermouse {
log.Log(GOCUI, "supermouse off")
me.supermouse = false
} else {
me.supermouse = true
log.Log(GOCUI, "supermouse on")
}
return nil
}
// use this to test code ideas // put whatever you want here and hit '2' to activate it
func theNotsure(g *gocui.Gui, v *gocui.View) error {
log.Info("got to theNotsure(). now what? dark =", me.dark)
me.refresh = true
log.Info("running VerifyParentId()")
me.treeRoot.VerifyParentId()
/*
if me.debug {
log.Info("debugging off")
me.debug = false
} else {
log.Info("debugging on")
me.debug = true
}
win := findWindowUnderMouse()
if win != nil {
win.dumpWidget("found() win. redrawing window:")
win.makeWindowActive()
}
*/
return nil
}
func theDarkness(g *gocui.Gui, v *gocui.View) error {
if me.dark {
me.dark = false
log.Info("you have seen the light")
} else {
me.dark = true
log.Info("you have entered into darkness (you may need to trigger SIGWINCH)")
log.Info("or maybe open a new window. notsure. This obviously isn't finished.")
log.Info("submit patches to this and you will definitely get free cloud credits at WIT")
}
return nil
}
func wheelsUp(g *gocui.Gui, v *gocui.View) error {
stdoutWheelsUp()
return nil
}
func wheelsDown(g *gocui.Gui, v *gocui.View) error {
stdoutWheelsDown()
return nil
}
func tabCycleWindows(g *gocui.Gui, v *gocui.View) error {
// log.Info("try to switch windows here")
if len(me.allwin) != len(findWindows()) {
me.allwin = findWindows()
}
tk := findNextWindow()
if tk == nil {
log.Info("findNextWindow() err. returned nil")
return nil
}
tk.makeWindowActive()
return nil
}
func doEsc(g *gocui.Gui, v *gocui.View) error {
log.Info("got escape key")
if me.dropdown.active {
me.dropdown.tk.Hide()
me.dropdown.active = false
log.Info("escaped from dropdown")
me.baseGui.SetCurrentView(me.notify.clock.tk.cuiName)
}
if me.textbox.active {
me.textbox.tk.Hide()
me.textbox.active = false
log.Info("escaped from textbox")
me.baseGui.SetCurrentView(me.notify.clock.tk.cuiName)
}
return nil
}
func theShow(g *gocui.Gui, v *gocui.View) error {
var w *guiWidget
w = me.treeRoot.TK.(*guiWidget)
w.showWidgets()
return nil
}
func doExit(g *gocui.Gui, v *gocui.View) error {
standardExit()
return nil
}
func doPanic(g *gocui.Gui, v *gocui.View) error {
log.Log(GOCUI, "do panic() here")
standardClose()
panic("forced panic in gocui")
}
func openDebuggger(g *gocui.Gui, v *gocui.View) error {
me.myTree.SendEnableDebugger()
return nil
}
func theFind(g *gocui.Gui, v *gocui.View) error {
w, h := g.MousePosition()
for _, tk := range findByXY(w, h) {
// tk.v.BgColor = gocui.ColorGreen
tk.dumpWidget("theFind()")
// tk.verifyRect()
}
return nil
}
func reverseStdout(g *gocui.Gui, v *gocui.View) error {
if me.stdout.reverse {
me.stdout.reverse = false
log.Info("stdout scrolling normal")
} else {
me.stdout.reverse = true
log.Info("stdout scrolling is reversed. this is sometimes useful when you")
log.Info("only need to see a few most recent lines and have the STDOUT window")
log.Info("take up minimal realestate at the bottom of your window")
}
return nil
}
// is run whenever anyone hits 'd' (in an open space)
func theLetterD(g *gocui.Gui, v *gocui.View) error {
// widgets that don't have physical existance in
// a display toolkit are hidden. In the case
// of gocui, they are set as not 'visible' and put offscreen
// or have the size set to zero
// (hopefully anyway) lots of things with the toolkit
// still don't work
fakeStartWidth = me.FakeW
fakeStartHeight = me.TabH + me.FramePadH
if me.showDebug {
showFake()
me.showDebug = false
} else {
hideFake()
me.showDebug = true
}
return nil
}
func theHelp(g *gocui.Gui, v *gocui.View) error {
if me.showHelp {
log.Info("Show the help!")
showHelp()
} else {
log.Info("Hide the help!")
hideHelp()
}
return nil
}
// todo: find and give credit to the person that I found this patch in their forked repo
// handle ctrl+z
func handle_ctrl_z(g *gocui.Gui, v *gocui.View) error {
gocui.Suspend()
log.Info("got ctrl+z")
syscall.Kill(syscall.Getpid(), syscall.SIGSTOP)
log.Info("got ctrl+z syscall() done")
gocui.Resume()
return nil
}

View File

@ -1,131 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
)
func theStdout(g *gocui.Gui, v *gocui.View) error {
// me.stdout.pager = 0
infos := fmt.Sprintf("pager=%d len(%d) ", me.stdout.pager, len(me.stdout.outputS))
infos += fmt.Sprintf("last(%d,%d)", me.stdout.lastW, me.stdout.lastH)
me.stdout.changed = true
if me.stdout.outputOnTop {
if me.stdout.outputOffscreen {
me.stdout.outputOffscreen = false
log.Info("stdout moved off screen", infos)
me.stdout.lastW = me.stdout.tk.gocuiSize.w0
me.stdout.lastH = me.stdout.tk.gocuiSize.h0
relocateStdoutOffscreen()
new1 := new(tree.ToolkitConfig)
new1.Plugin = "gocui"
new1.Name = "stdoutoffscreen"
new1.Value = "true"
me.myTree.ConfigSave(new1)
return nil
} else {
me.stdout.outputOffscreen = true
log.Info("stdout moved on screen", infos)
new1 := new(tree.ToolkitConfig)
new1.Plugin = "gocui"
new1.Name = "stdoutoffscreen"
new1.Value = "false"
me.myTree.ConfigSave(new1)
}
// move the stdout window back onscreen
me.stdout.tk.relocateStdout(me.stdout.lastW, me.stdout.lastH)
me.stdout.outputOnTop = false
setThingsOnTop()
new1 := new(tree.ToolkitConfig)
new1.Plugin = "gocui"
new1.Name = "stdoutlevel"
new1.Value = "bottom"
me.myTree.ConfigSave(new1)
} else {
me.stdout.outputOnTop = true
setThingsOnTop()
new1 := new(tree.ToolkitConfig)
new1.Plugin = "gocui"
new1.Name = "stdoutlevel"
new1.Value = "top"
me.myTree.ConfigSave(new1)
}
return nil
}
func stdoutPgup(g *gocui.Gui, v *gocui.View) error {
me.stdout.pager -= me.stdout.Height() - 2
if me.stdout.pager < 0 {
me.stdout.pager = 0
}
tk := me.stdout.tk
tk.refreshStdout()
return nil
}
func stdoutHome(g *gocui.Gui, v *gocui.View) error {
me.stdout.pager = 0
me.stdout.tk.refreshStdout()
return nil
}
func stdoutArrowUp(g *gocui.Gui, v *gocui.View) error {
if me.stdout.reverse {
stdoutWheelsDown()
} else {
stdoutWheelsUp()
}
return nil
}
func stdoutArrowDown(g *gocui.Gui, v *gocui.View) error {
if me.stdout.reverse {
stdoutWheelsUp()
} else {
stdoutWheelsDown()
}
return nil
}
func stdoutPgdn(g *gocui.Gui, v *gocui.View) error {
win := findWindowUnderMouse()
if win != nil {
if win.full.Height() > 50 {
log.Info("paging through really large window pager =", win.window.pager)
win.window.pager += 10
return nil
}
}
me.stdout.pager += 10
tk := me.stdout.tk
tk.refreshStdout()
return nil
}
// scrolling up with the mouse wheel (or trackpad)
func stdoutWheelsUp() {
// log.Info("private wheels up")
me.stdout.pager -= 2
if me.stdout.pager < 0 {
me.stdout.pager = 0
}
me.stdout.tk.refreshStdout()
}
// scrolling down with the mouse wheel (or trackpad)
func stdoutWheelsDown() {
// log.Info("you've landed")
me.stdout.pager += 2
if me.stdout.pager > len(me.stdout.outputS) {
me.stdout.pager = len(me.stdout.outputS)
}
me.stdout.tk.refreshStdout()
}

View File

@ -1,74 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"time"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
func mouseUp(g *gocui.Gui, v *gocui.View) error {
// useful to debug everything that is being clicked on
/*
for _, tk := range findByXY(w, h) {
tk.dumpWidget("mouseUp()")
}
*/
me.mouse.mouseUp = true
me.mouse.currentDrag = nil
if me.mouse.double && (time.Since(me.mouse.down) < me.mouse.doubletime) {
me.mouse.double = false
doMouseDoubleClick(me.mouse.downW, me.mouse.downH)
return nil
}
me.mouse.double = false
if time.Since(me.mouse.down) < me.mouse.clicktime {
doMouseClick(me.mouse.downW, me.mouse.downH)
}
return nil
}
// this is where you have to figure out what
// widget was underneath so you can active
// the right response for the toolkit user's app
func mouseDown(g *gocui.Gui, v *gocui.View) error {
if me.mouse.mouseUp {
if time.Since(me.mouse.down) < me.mouse.doubletime {
me.mouse.double = true
}
me.mouse.mouseUp = false
me.mouse.down = time.Now()
w, h := g.MousePosition()
me.mouse.downW = w
me.mouse.downH = h
win := findWindowUnderMouse()
if win != nil {
w, h := g.MousePosition()
s := fmt.Sprintf("mouse(%d,%d) ", w, h)
offW := win.full.w1 - w
offH := win.full.h1 - h
s += fmt.Sprintf("corner(%d,%d)", offW, offH)
if (offW < 3) && (offH < 3) {
log.Info("attempting resize on ", s, win.cuiName)
me.mouse.resize = true
// store the stdout corner for computing the drag size
me.stdout.lastW = me.stdout.tk.gocuiSize.w0
me.stdout.lastH = me.stdout.tk.gocuiSize.h0
} else {
// log.Info("mouse down resize off", s)
me.mouse.resize = false
}
win.setAsDragging()
}
}
return nil
}

View File

@ -1,161 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
func (tk *guiWidget) doButtonClick() {
if tk.IsEnabled() {
tk.dumpWidget("click()") // enable this to debug widget clicks
me.myTree.SendFromUser(tk.node)
} else {
log.Info("button is currently disabled by the application")
// tk.dumpWidget("disabled()") // enable this to debug widget clicks
}
}
// handles a mouse click
func doMouseClick(w int, h int) {
// Flag widgets (dropdown menus, etc) are the highest priority. ALWAYS SEND MOUSE CLICKS THERE FIRST
// handle an open dropdown menu or text entry window first
if me.dropdown.active || me.textbox.active {
// can't drag or do anything when dropdown or textbox are visible
for _, tk := range findByXY(w, h) {
if tk.WidgetId() == me.dropdown.wId {
log.Info("got dropdown click", w, h, tk.cuiName)
tk.dropdownClicked(w, h)
return
}
if tk.WidgetId() == me.textbox.wId {
log.Info("got textbox click", w, h, tk.cuiName)
textboxClosed()
return
}
}
log.Info("a dropdown or textbox is active. you can't click anywhere else (otherwise hit ESC)", w, h)
return
}
win := findWindowUnderMouse()
if win == nil {
log.Log(INFO, "click() nothing was at:", w, h)
log.Log(INFO, "click() check if", w, h, "is the libnotify icon")
if me.notify.icon.tk != nil && me.notify.icon.tk.gocuiSize.inRect(w, h) {
log.Log(GOCUI, "click() is libnotify.icon!")
if me.notify.icon.active {
log.Info("show notify.icon here")
setNotifyIconText("[X]")
me.notify.icon.active = false
} else {
log.Info("hide notify.icon here")
setNotifyIconText("[ ]")
me.notify.icon.active = true
}
return
}
if me.notify.clock.tk != nil && me.notify.clock.tk.gocuiSize.inRect(w, h) {
log.Log(GOCUI, "click() is the clock!")
if me.showHelp {
log.Info("show help")
showHelp()
} else {
log.Info("hide help")
hideHelp()
}
return
}
return
}
if !win.isWindowActive() {
win.makeWindowActive()
return
} else {
// potentally the user is closing the window
if win.checkWindowClose(w, h) {
return
}
}
// look in this window for widgets
// widgets have priority. send the click here first
for _, tk := range win.findByXYreal(w, h) {
switch tk.WidgetType() {
case widget.Checkbox:
if tk.Checked() {
log.Log(WARN, "checkbox is being set to false")
tk.SetChecked(false)
tk.setCheckbox()
} else {
log.Log(WARN, "checkbox is being set to true")
tk.SetChecked(true)
tk.setCheckbox()
}
me.myTree.SendUserEvent(tk.node)
return
case widget.Button:
tk.doButtonClick()
return
case widget.Combobox:
tk.showDropdown()
return
case widget.Dropdown:
tk.showDropdown()
return
case widget.Textbox:
log.Log(WARN, "TODO: textbox click")
tk.prepTextbox()
return
case widget.Label:
if tk.node.InTable() {
if tk.node.State.AtH == 0 {
log.Log(NOW, "todo: sort by column here")
tk.dumpWidget("sort")
}
}
return
default:
// TODO: enable the GUI debugger in gocui
// tk.dumpWidget("undef click()") // enable this to debug widget clicks
}
}
}
// todo: use this?
func ctrlDown(g *gocui.Gui, v *gocui.View) error {
log.Info("todo: clicked with ctrlDown")
return nil
}
func doMouseDoubleClick(w int, h int) {
me.mouse.double = false
// log.Printf("actually a double click (%d,%d)", w, h)
if me.dropdown.active || me.textbox.active {
// can't drag or do anything when dropdown or textbox are visible
log.Info("can't double click. dropdown or textbox is active")
return
}
for _, tk := range findByXY(w, h) {
if tk.WidgetType() == widget.Window {
tk.makeWindowActive()
return
}
if tk.WidgetType() == widget.Stdout {
if me.stdout.outputOnTop {
me.stdout.outputOnTop = false
setThingsOnTop()
} else {
me.stdout.outputOnTop = true
setThingsOnTop()
}
return
}
}
}

View File

@ -1,136 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// 2025 note by jcarr:
// this is one of the coolest things ever worked with.
// Personally, I've been working on making a gocui GO plugin
// so I can use it as a generalized console GUI toolkit.
//
// Well done everyone that has contributed to this gocui project !!!
// I am in your debt. Happy hacking & peace.
package main
import (
"fmt"
"time"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/widget"
)
// this function uses the mouse position to highlight & unhighlight things
// this is run every time the user moves the mouse over the terminal window
func mouseMove(g *gocui.Gui) {
// this runs while the user moves the mouse. this highlights text
// toggle off all highlight views except for whatever is under the mouse
// START HIGHLIGHTING
for _, view := range g.Views() {
view.Highlight = false
}
w, h := g.MousePosition()
// TODO: try to highlight entire grid rows
if v, err := g.ViewByPosition(w, h); err == nil {
// block anything from highlighting while a dialog box is open
if me.dropdown.active || me.textbox.active {
if me.dropdown.tk != nil && me.dropdown.tk.v == v {
v.Highlight = true
}
if me.textbox.tk != nil && me.textbox.tk.v == v {
v.Highlight = true
}
} else {
v.Highlight = true
}
}
// old hack. create the 'msg' view if it does not yet exist
// TODO: put this somewhere more correct
if widgetView, _ := g.View("msg"); widgetView == nil {
me.stdout.changed = true
if createStdout(g) {
return
}
return
}
// END HIGHLIGHTING
// Super Mouse Mode. very useful for debugging in the past. also, just fun
if me.supermouse {
w, h := g.MousePosition()
for _, tk := range findByXY(w, h) {
s := fmt.Sprintf("SM (%3d,%3d)", w, h)
tk.dumpWidget(s)
}
}
if me.mouse.mouseUp {
return
}
// EVERYTHING BELOW THIS IS RELATED TO MOUSE DRAGGING
// has the mouse been pressed down long enough to start dragging?
if time.Since(me.mouse.down) < me.mouse.clicktime {
// not dragging
return
}
if me.dropdown.active || me.textbox.active {
// can't drag
return
}
// drag whatever was set to drag
if me.mouse.currentDrag != nil {
// me.mouse.currentDrag.dumpWidget(fmt.Sprintf("MM (%3d,%3d)", w, h))
me.mouse.currentDrag.moveNew()
return
}
log.Info(fmt.Sprintf("gocui gui toolkit plugin error. nothing to drag at (%d,%d)", w, h))
return
}
func (tk *guiWidget) setAsDragging() {
me.mouse.currentDrag = tk
tk.lastW = tk.gocuiSize.w0
tk.lastH = tk.gocuiSize.h0
}
// this is how the window gets dragged around
func (tk *guiWidget) moveNew() {
w, h := me.baseGui.MousePosition()
if tk.WidgetType() == widget.Window {
tk.window.wasDragged = true
// compute the new location based off how far the mouse has moved
// since the mouse button was pressed down
tk.gocuiSize.w0 = tk.lastW + w - me.mouse.downW
tk.gocuiSize.h0 = tk.lastH + h - me.mouse.downH
tk.makeWindowActive()
return
}
if tk.WidgetType() == widget.Stdout {
if me.mouse.resize {
newW := w - me.stdout.lastW
newH := h - me.stdout.lastH
me.stdout.w = newW
me.stdout.h = newH
// log.Info("Resize true", w, h, newW, newH)
// me.stdout.lastW = w - me.stdout.mouseOffsetW
// me.stdout.lastH = h - me.stdout.mouseOffsetH
tk.relocateStdout(me.stdout.lastW, me.stdout.lastH)
} else {
// compute the new location based off how far the mouse has moved
// since the mouse button was pressed down
newW := tk.lastW + w - me.mouse.downW
newH := tk.lastH + h - me.mouse.downH
tk.relocateStdout(newW, newH)
// log.Info("Resize false", w, h, newW, newH)
}
setThingsOnTop() // sets help, Stdout, etc on the top after windows have been redrawn
me.stdout.changed = true
}
}

58
fakefile.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"bytes"
"errors"
"io"
)
type FakeFile struct {
reader *bytes.Reader
buffer *bytes.Buffer
offset int64
}
func (f *FakeFile) Read(p []byte) (n int, err error) {
n, err = f.reader.ReadAt(p, f.offset)
f.offset += int64(n)
return n, err
}
func (f *FakeFile) Write(p []byte) (n int, err error) {
n, err = f.buffer.Write(p)
f.offset += int64(n)
f.reader.Reset(f.buffer.Bytes())
return n, err
}
func (f *FakeFile) Seek(offset int64, whence int) (int64, error) {
newOffset := f.offset
switch whence {
case io.SeekStart:
newOffset = offset
case io.SeekCurrent:
newOffset += offset
case io.SeekEnd:
newOffset = int64(f.buffer.Len()) + offset
default:
return 0, errors.New("Seek: whence not at start,current or end")
}
// never can get here right?
if newOffset < 0 {
return 0, errors.New("Seek: offset < 0")
}
f.offset = newOffset
return f.offset, nil
}
func NewFakeFile() *FakeFile {
buf := &bytes.Buffer{}
return &FakeFile{
reader: bytes.NewReader(buf.Bytes()),
buffer: buf,
offset: 0,
}
}

218
find.go
View File

@ -1,218 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"slices"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/widget"
)
/*
gocui defines the offset like this:
width -> increases to the right
---------------------------------- hieght
| H = 1 | increases
| | |
| W = 1 W = 18 | |
| | v
| H = 5 | downwards
-------------------------------------
*/
// change over to this name
// returns all the widgets under (X,H) that are visible
func findByXY(w int, h int) []*guiWidget {
rootW := me.treeRoot.TK.(*guiWidget)
// this searches the binary tree recursively (function is right below)
return rootW.findByXYreal(w, h)
}
func (r rectType) inRect(w int, h int) bool {
if (r.w0 <= w) && (w <= r.w1) && (r.h0 <= h) && (h <= r.h1) {
return true
}
return false
}
// this checks a widget to see if it is under (W,H), then checks the widget's children
// anything that matches is passed back as an array of widgets
func (tk *guiWidget) findByXYreal(w int, h int) []*guiWidget {
var widgets []*guiWidget
// if !tk.Visible() {
// ignore widgets that are not visible
// } else {
// check the location to see if this is under (W,H)
// if it is, return this widget
// if (tk.gocuiSize.w0 <= w) && (w <= tk.gocuiSize.w1) &&
// (tk.gocuiSize.h0 <= h) && (h <= tk.gocuiSize.h1) {
// if tk.gocuiSize.inRect(w, h) {
// widgets = append(widgets, tk)
// } else {
// if (tk.full.w0 <= w) && (w <= tk.full.w1) &&
// (tk.full.h0 <= h) && (h <= tk.full.h1) {
if tk.full.inRect(w, h) {
widgets = append(widgets, tk)
}
// log.Log(GOCUI, "findByXY() found", widget.WidgetType(), w, h)
// }
// }
// tk.verifyRect()
// search through the children widgets in the binary tree
for _, child := range tk.children {
widgets = append(widgets, child.findByXYreal(w, h)...)
}
return widgets
}
// returns all the windows from the root of the binary tree
func findWindows() []*guiWidget {
rootW := me.treeRoot.TK.(*guiWidget)
return rootW.findWindows()
}
// walk the binary tree looking for WidgetType == Window
func (tk *guiWidget) findWindows() []*guiWidget {
var found []*guiWidget
if tk.WidgetType() == widget.Window {
found = append(found, tk)
}
for _, child := range tk.children {
found = append(found, child.findWindows()...)
}
return found
}
// used by gocui.TabKey to rotate through the windows
func findNextWindow() *guiWidget {
var found bool
if len(me.allwin) == 0 {
return nil
}
for _, tk := range me.allwin {
if tk.window.active {
found = true
continue
}
if found {
return tk
}
}
// at the end, loop to the beginning
return me.allwin[0]
}
// find the window under the mouse and only the window under the mouse
func findWindowUnderMouse() *guiWidget {
w, h := me.baseGui.MousePosition()
if len(me.allwin) != len(findWindows()) {
me.allwin = findWindows()
}
// if the stdout window is on top, check it first
if me.stdout.outputOnTop {
if me.stdout.tk.full.inRect(w, h) {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s stdout on top (%dx%d)", me.stdout.tk.cuiName, w, h))
return me.stdout.tk
}
}
// now check if the active window is below the mouse
for _, tk := range me.allwin {
if tk.window.active {
if tk.full.inRect(w, h) {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s active window (%dx%d)", tk.cuiName, w, h))
return tk
}
}
}
// well, just find any window then
// sorting by order might work?
slices.SortFunc(me.allwin, func(a, b *guiWidget) int {
return a.window.order - b.window.order
})
for _, win := range me.allwin {
if win.full.inRect(w, h) {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s window (%dx%d)", win.cuiName, w, h))
return win
}
}
// okay, no window. maybe the stdout is there?
if me.stdout.tk.full.inRect(w, h) {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s stdout (%dx%d)", me.stdout.tk.cuiName, w, h))
return me.stdout.tk
}
// geez. nothing! maybe auto return stdout?
log.Info("findWindowUnderMouse() no window found at", w, h)
return nil
}
func (tk *guiWidget) findParentWindow() *guiWidget {
if tk.WidgetType() == widget.Window {
return tk
}
if tk.parent == nil {
return nil
}
return tk.parent.findParentWindow()
}
func (tk *guiWidget) findWidgetByName(name string) *guiWidget {
if tk.cuiName == name {
return tk
}
for _, child := range tk.children {
found := child.findWidgetByName(name)
if found != nil {
return found
}
}
return nil
}
func (tk *guiWidget) findWidgetByView(v *gocui.View) *guiWidget {
if tk.v == v {
return tk
}
if tk.cuiName == v.Name() {
log.Log(NOW, "findWidget() error. names are mismatched or out of sync", tk.cuiName)
log.Log(NOW, "findWidget() or maybe the view has been deleted")
// return tk
}
for _, child := range tk.children {
found := child.findWidgetByView(v)
if found != nil {
return found
}
}
return nil
}
func (tk *guiWidget) findWidgetById(id int) *guiWidget {
if tk.WidgetId() == id {
return tk
}
for _, child := range tk.children {
found := child.findWidgetById(id)
if found != nil {
return found
}
}
return nil
}

21
go.mod Normal file
View File

@ -0,0 +1,21 @@
module go.wit.com/toolkits/gocui
go 1.21.4
require (
github.com/awesome-gocui/gocui v1.1.0
go.wit.com/log v0.13.0
go.wit.com/widget v1.1.8
)
require (
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.4.0 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/mattn/go-runewidth v0.0.10 // indirect
github.com/rivo/uniseg v0.1.0 // indirect
go.wit.com/dev/davecgh/spew v1.1.4 // indirect
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/text v0.3.3 // indirect
)

26
go.sum Normal file
View File

@ -0,0 +1,26 @@
github.com/awesome-gocui/gocui v1.1.0 h1:db2j7yFEoHZjpQFeE2xqiatS8bm1lO3THeLwE6MzOII=
github.com/awesome-gocui/gocui v1.1.0/go.mod h1:M2BXkrp7PR97CKnPRT7Rk0+rtswChPtksw/vRAESGpg=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.4.0 h1:W6dxJEmaxYvhICFoTY3WrLLEXsQ11SaFnKGVEXW57KM=
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
go.wit.com/dev/davecgh/spew v1.1.4 h1:C9hj/rjlUpdK+E6aroyLjCbS5MFcyNUOuP1ICLWdNek=
go.wit.com/dev/davecgh/spew v1.1.4/go.mod h1:sihvWmnQ/09FWplnEmozt90CCVqBtGuPXM811tgfhFA=
go.wit.com/log v0.13.0 h1:0vyW3mHwDww3wCsyGnmQuX2P4V7aBMoJgxCy0GfA20g=
go.wit.com/log v0.13.0/go.mod h1:BaJBfHFqcJSJLXGQ9RHi3XVhPgsStxSMZRlaRxW4kAo=
go.wit.com/widget v1.1.8 h1:5cHcmfgwCyHjf02Af/9UMrbLhBR7Z/LFAjPuCx4dx5A=
go.wit.com/widget v1.1.8/go.mod h1:I8tnD3x3ECbB/CRNnLCdC+uoyk7rK0AEkzK1bQYSqoQ=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -1,16 +1,45 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
// This initializes the gocui package
// it runs SetManagerFunc which passes every input
// event (keyboard, mouse, etc) to the function "gocuiEvent()"
func gocuiMain() {
g, err := gocui.NewGui(gocui.OutputNormal, true)
if err != nil {
panic(err)
}
defer g.Close()
me.baseGui = g
g.Cursor = true
g.Mouse = true
// this sets the function that is run on every event. For example:
// When you click the mouse, move the mouse, or press a key on the keyboard
// This is equivalent to xev or similar to cat /dev/input on linux
g.SetManagerFunc(gocuiEvent)
if err := defaultKeybindings(g); err != nil {
panic(err)
}
if err := g.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) {
panic(err)
}
}
// Thanks to the gocui developers -- your package kicks ass
// This function is called on every event. It is a callback function from the gocui package
// which has an excellent implementation. While gocui handles things like text highlighting
@ -18,11 +47,29 @@ import (
// complicated console handling, it sends events here in a clean way.
// This is equivalent to the linux command xev (apt install x11-utils)
func gocuiEvent(g *gocui.Gui) error {
me.ecount += 1
maxX, maxY := g.Size()
mx, my := g.MousePosition()
log.Log(NOW, "handleEvent() START", maxX, maxY, mx, my, msgMouseDown)
if _, err := g.View("msg"); msgMouseDown && err == nil {
moveMsg(g)
}
if widgetView, _ := g.View("msg"); widgetView == nil {
log.Log(NOW, "handleEvent() create output widget now", maxX, maxY, mx, my)
makeOutputWidget(g, "this is a create before a mouse click")
if me.logStdout != nil {
// setOutput(me.logStdout)
}
} else {
log.Log(INFO, "output widget already exists", maxX, maxY, mx, my)
}
mouseMove(g)
log.Log(INFO, "handleEvent() END ", maxX, maxY, mx, my, msgMouseDown)
return nil
}
func dragOutputWindow() {
}
// turns off the frame on the global window
func setFrame(b bool) {
// TODO: figure out what this might be useful for
@ -34,42 +81,10 @@ func setFrame(b bool) {
v.Frame = b
}
// a test. exits gocui, but the application still runs
// maybe can switch toolkits?
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
func (tk *guiWidget) SetView() error {
if me.baseGui == nil {
return fmt.Errorf("me.baseGui == nil")
}
r := new(rectType)
r.w0 = tk.gocuiSize.w0
r.h0 = tk.gocuiSize.h0
r.w1 = tk.gocuiSize.w1
r.h1 = tk.gocuiSize.h1
return tk.SetViewRect(r)
}
func (tk *guiWidget) SetViewRect(r *rectType) error {
if me.baseGui == nil {
return fmt.Errorf("me.baseGui == nil")
}
var err error
tk.v, err = me.baseGui.SetView(tk.cuiName, r.w0, r.h0, r.w1, r.h1, 0)
if err != nil {
if !errors.Is(err, gocui.ErrUnknownView) {
log.Log(ERROR, "SetView() global failed on name =", tk.cuiName)
return err
}
}
return nil
}
func SetView(name string, x0, y0, x1, y1 int, overlaps byte) *gocui.View {
if me.baseGui == nil {
log.Log(ERROR, "SetView() ERROR: me.baseGui == nil")

85
help.go
View File

@ -1,7 +1,4 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// Prior Copyright 2014 The gocui Authors. All rights reserved.
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@ -13,51 +10,33 @@ import (
"strings"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
)
/*
This in helpText doesn't print
"\x1b[0;32m  \x1b[0m", // this was a test to see what might be
// possible with gocui. it doesn't seem to work for me
*/
var helpText []string = []string{"Help Menu",
var helpText []string = []string{"KEYBINDINGS",
"",
"Tab toggle through windows",
"'O' toggle STDOUT",
"'H' toggle this gocui menu",
"'D' toggle light/dark mode",
"CTRL-z background to shell",
"CTRL-c quit()",
"?: toggle help",
"d: toggle debugging",
"r: redraw widgets",
"s/h: show/hide all widgets",
"L: list all widgets",
"M: list all widgets positions",
"q: quit()",
"p: panic()",
"o: show Stdout",
"l: log to /tmp/witgui.log",
"Ctrl-D: Toggle Debugging",
"Ctrl-V: Toggle Verbose Debugging",
"Ctrl-C: Exit",
"",
"Debugging:",
"'S' Supermouse mode",
"'M' list all widget positions",
"'L' list all widgets in tree",
"<Pgup> scroll up the STDOUT window",
"<Pgdn> scroll down the STDOUT window",
"'r' reverse STDOUT scrolling",
}
func hideHelp() {
if me.showHelp {
log.Info("help is already down")
me.showHelp = true
return
}
me.showHelp = true
func hidehelplayout() {
me.baseGui.DeleteView("help")
// n.deleteView()
// child.hideFake()
}
func showHelp() error {
if !me.showHelp {
log.Info("help is already up")
me.showHelp = false
return nil
}
me.showHelp = false
func helplayout() error {
g := me.baseGui
var err error
maxX, _ := g.Size()
@ -69,18 +48,17 @@ func showHelp() error {
}
}
a := maxX - (newW + me.FramePadW)
b := me.notify.help.offsetH
c := maxX - 1
d := me.notify.help.offsetH + len(helpText) + me.FramePadH
help, err := g.SetView("help", a, b, c, d, 0)
help, err := g.SetView("help", maxX-(newW+me.FramePadW), 0, maxX-1, len(helpText)+me.FramePadH, 0)
if err != nil {
if !errors.Is(err, gocui.ErrUnknownView) {
return err
}
help.SelBgColor = gocui.ColorGreen
help.SelFgColor = gocui.ColorBlack
// fmt.Fprintln(help, "Enter: Click Button")
// fmt.Fprintln(help, "Tab/Space: Switch Buttons")
// fmt.Fprintln(help, "Backspace: Delete Button")
// fmt.Fprintln(help, "Arrow keys: Move Button")
fmt.Fprintln(help, strings.Join(helpText, "\n"))
@ -88,21 +66,6 @@ func showHelp() error {
return err
}
}
g.SetViewOnTop("help")
me.helpLabel = help
/*
if me.treeRoot == nil {
log.Info("gogui makeClock() error. treeRoot == nil")
return nil
} else {
if me.stdout.tk == nil {
makeOutputWidget(me.baseGui, "made this in showHelp()")
msg := fmt.Sprintf("test to stdout from in showHelp() %d\n", me.ecount)
me.stdout.Write([]byte(msg))
log.Log(NOW, "log.log(NOW) test")
}
}
*/
return nil
}

464
init.go
View File

@ -1,464 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"os"
"runtime"
"runtime/debug"
"runtime/pprof"
"strconv"
"strings"
"time"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
)
// sent via -ldflags
var VERSION string
var BUILDTIME string
var PLUGIN string = "gocui"
// this is called at the very initial connection
// between the app and this gocui plugin
// this is a good place to initialize gocui's default behavior
func toolkitInit() {
log.Log(INFO, "gocui toolkitInit() me.ok =", me.ok)
if me.baseGui == nil {
log.Info("gocui baseGui is still nil")
standardExit()
}
if me.treeRoot == nil {
log.Info("gocui treeRoot is still nil")
standardExit()
}
// w := me.treeRoot.TK.(*guiWidget)
// w.dumpTree("MM")
// w.dumpWindows("WW")
// SETUP HELP START
me.baseGui.Update(testRefresh)
log.Log(INFO, "gocui toolkitInit() trying showHelp() me.ok =", me.ok)
showHelp()
hideHelp()
// SETUP HELP END
// SETUP STDOUT START
if me.stdout.tk == nil {
makeOutputWidget(me.baseGui, "from setThingsOnTop()")
}
// time.Sleep(300 * time.Millisecond)
log.Log(INFO, "gocui toolkitInit() me.ok =", me.ok)
me.baseGui.Update(testRefresh)
if !me.stdout.init {
log.Log(INFO, "gocui toolkitInit() stdout.Init me.ok =", me.ok)
me.stdout.init = true
relocateStdoutOffscreen()
}
// time.Sleep(1 * time.Second)
me.stdout.outputOnTop = false
setThingsOnTop()
// SETUP STDOUT END
// SETUP BG
if me.BG.tk == nil {
me.BG.tk = makeNewInternalWidget(me.BG.wId)
}
// SETUP libnotify clock and menu
me.notify.clock.once.Do(makeNotifyClock)
me.notify.icon.once.Do(makeNotifyIcon)
// TODO: for some reason, this makes the background doesn't display
// PUT INIT DEBUG COOE HERE
var toggle bool
for i := 0; i < 4; i++ {
// enable this to show early debugging
// w := me.treeRoot.TK.(*guiWidget)
// w.dumpTree("MM")
// w.dumpWindows("WW")
time.Sleep(100 * time.Millisecond)
if toggle {
toggle = false
// log.Info("gocui toolkitInit() put testing true stuff here")
} else {
toggle = true
// log.Info("gocui toolkitInit() put testing false stuff here")
}
setBottomBG()
}
// PUT INIT DEBUG COOE HERE END
// TEST TEXTBOX START
// time.Sleep(1 * time.Second)
log.Log(INFO, "gocui toolkitInit() me.ok =", me.ok)
me.baseGui.Update(testRefresh)
if me.textbox.tk == nil {
log.Log(INFO, "gocui toolkitInit() initTextbox me.ok =", me.ok)
initTextbox()
}
// TEST TEXTBOX END
}
func toolkitClose() {
me.baseGui.Close()
}
// a GO GUI plugin should initTree in init()
// this should be done before the application
// starts trying to open up a channel
func init() {
me.myTree = initTree()
}
// sets defaults and establishes communication
// to this toolkit from the wit/gui golang package
func initPlugin() {
defer func() {
if r := recover(); r != nil {
fmt.Fprintf(me.outf, "PANIC: initPlugin() recovered %v\n", r)
return
}
}()
var err error
// read in defaults from config protobuf
if val, err := me.myTree.ConfigFind("stdout"); err == nil {
if val == "true" {
me.stdout.startOnscreen = true
// me.stdout.Write([]byte("starting with stdout onscreen\n"))
}
if val == "disable" {
log.Log(INFO, "gocui: attempt to COMPLETELY DISABLE STDOUT LOG")
me.stdout.disable = true
}
}
if val, err := me.myTree.ConfigFind("stdoutoffscreen"); err == nil {
if val == "false" {
// log.Log(NOW, "gocui: START ON SCREEN TRUE")
me.stdout.startOnscreen = true
}
}
if val, err := me.myTree.ConfigFind("dark"); err == nil {
if val == "true" {
me.dark = true
}
} else {
// macos iterm2 really only works with dark mode right now
if runtime.GOOS == "macos" {
me.dark = true
}
}
// todo: make this a tmp file that goes away
if !me.stdout.disable {
tmpFile, err := os.CreateTemp("", "gocui-*.log")
if err != nil {
fmt.Println("Error creating temp file:", err)
standardExit()
}
// defer os.Remove(tmpFile.Name())
log.Log(INFO, "stdout.disable == true. writing to", tmpFile.Name())
me.outf = tmpFile
// todo: some early output still goes to the /tmp/ file
//time.Sleep(200 * time.Millisecond)
log.CaptureMode(me.stdout)
}
me.starttime = time.Now()
log.Log(INFO, "Init() of awesome-gocui")
// init the config struct default values
Set(&me, "default")
// initial app window settings
// initial stdout window settings
me.stdout.w = 180
me.stdout.h = 40
me.stdout.lastW = 4
me.stdout.lastH = 20
if val, err := me.myTree.ConfigFind("stdoutsize"); err == nil {
parts := strings.Fields(val)
if len(parts) == 4 {
log.Info("initial stdout settings:", parts, "setting startOnscreen = true")
me.stdout.w, _ = strconv.Atoi(parts[0])
me.stdout.h, _ = strconv.Atoi(parts[1])
me.stdout.lastW, _ = strconv.Atoi(parts[2])
me.stdout.lastH, _ = strconv.Atoi(parts[3])
me.stdout.startOnscreen = true
} else {
log.Info("initial stdout settings parse error:", parts)
}
}
// just make up unique values for these
me.dropdown.wId = -77
me.textbox.wId = -55
me.stdout.wId = -4
me.BG.wId = -22
// the clock widget id and offset
me.notify.clock.wId = -5
me.notify.clock.offsetW = 13
me.notify.clock.offsetH = 1
me.notify.icon.wId = -6
me.notify.icon.offsetW = 4
me.notify.icon.offsetH = 1
me.notify.help.wId = -7
me.notify.help.offsetH = 3
Set(&me.dropdown, "default")
// s := fmt.Sprintln("fake default check =", me.FakeW, "dropdown.Id", me.dropdown.Id)
// me.stdout.Write([]byte(s))
me.mouse.mouseUp = true
me.mouse.clicktime = time.Millisecond * 200
me.mouse.doubletime = time.Millisecond * 400
me.newWindowTrigger = make(chan *guiWidget, 1)
go newWindowTrigger()
go refreshGocui()
log.Log(NOW, "Init() start pluginChan")
if me.stdout.disable {
log.Info("Using STDOUT")
} else {
log.Info("Using gocui STDOUT")
os.Stdout = me.outf
log.CaptureMode(me.outf)
}
// init gocui
g, err := gocui.NewGui(gocui.OutputNormal, true)
if err != nil {
os.Exit(-1)
return
}
me.baseGui = g
g.Cursor = true
g.Mouse = true
// this sets the function that is run on every event. For example:
// When you click the mouse, move the mouse, or press a key on the keyboard
// This is equivalent to xev or similar to cat /dev/input on linux
g.SetManagerFunc(gocuiEvent)
// register how the 'gocui' will work as a GO toolkit plugin
// all applications will use these keys. they are universal.
// registered event handlers still have the events sent to gocuiEvent() above
registerHandlers(g)
time.Sleep(100 * time.Millisecond)
if me.outf != nil {
fmt.Fprintln(me.outf, "hello world", time.Since(me.starttime))
}
// coreStdout()
// createStdout(g)
// tell 'tree' that we are okay to start talking to
me.myTree.InitOK()
me.ok = true // this tells init() it's okay to work with gocui
go gocuiMain()
}
// This goroutine sits in gocui's MainLoop()
func gocuiMain() {
defer func() {
if r := recover(); r != nil {
log.Warn("PANIC ecovered in gocuiMain()", r)
if me.outf == nil {
debug.PrintStack()
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
panic(os.Stdout)
} else {
fmt.Fprintf(me.outf, "PANIC recovered in r = %v", r)
os.Stderr = me.outf
os.Stdout = me.outf
debug.PrintStack()
pprof.Lookup("goroutine").WriteTo(me.outf, 1)
panic(me.outf)
}
}
}()
// me.stdout.Write([]byte("begin gogui.MainLoop()\n"))
if err := me.baseGui.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) {
log.Log(NOW, "g.MainLoop() panic err =", err)
// normally panic here
panic("gocuiTKmainloop OOPS")
}
}
func standardExit() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
if me.outf != nil {
log.Log(NOW, "standardExit() doing outf.Close()")
me.outf.Close()
os.Remove(me.outf.Name())
}
// log(true, "standardExit() setOutput(os.Stdout)")
// setOutput(os.Stdout)
log.Log(NOW, "standardExit() send back Quit()")
// go sendBackQuit() // don't stall here in case the
// induces a delay in case the callback channel is broken
time.Sleep(200 * time.Millisecond)
log.Log(NOW, "standardExit() exit()")
os.Exit(0)
}
func standardClose() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
log.Log(NOW, "standardExit() doing outf.Close()")
me.outf.Close()
os.Remove(me.outf.Name())
// os.Stdin = os.Stdin
// os.Stdout = os.Stdout
// os.Stderr = os.Stderr
log.Log(NOW, "standardExit() send back Quit()")
}
func main() {
}
// this hack is to wait for the application to send something
// before trying to do anything. todo: rethink this someday
func waitOK() {
for {
if me.ok {
return
}
time.Sleep(10 * time.Millisecond)
}
}
// this hack is to wait for the application to send something
// before trying to do anything. todo: rethink this someday
func waitFirstWindow() {
for {
if me.firstWindowOk {
return
}
time.Sleep(10 * time.Millisecond)
}
}
// empty function. this triggers gocui to refresh the screen
func testRefresh(*gocui.Gui) error {
// log.Info("in testRefresh")
return nil
}
// refresh the screen 10 times a second
func refreshGocui() {
defer func() {
if r := recover(); r != nil {
if me.outf == nil {
log.Info("INIT PLUGIN recovered in r", r)
} else {
fmt.Fprintln(me.outf, "INIT PLUGIN recovered in r", r)
}
return
}
}()
var lastRefresh time.Time
lastRefresh = time.Now()
me.refresh = false
for {
time.Sleep(100 * time.Millisecond)
// log.Info("refresh checking ok")
if !me.ok {
continue
}
// redraw the windows if something has changed
if time.Since(lastRefresh) > 1000*time.Millisecond {
if me.refresh {
log.Log(NOW, "newWindowTrigger() sending refresh to channel")
me.newWindowTrigger <- me.treeRoot.TK.(*guiWidget)
me.refresh = false
}
if me.stdout.changed {
me.stdout.changed = false
lastRefresh = time.Now()
new1 := new(tree.ToolkitConfig)
new1.Plugin = "gocui"
new1.Name = "stdoutsize"
width := me.stdout.tk.gocuiSize.w1 - me.stdout.tk.gocuiSize.w0
height := me.stdout.tk.gocuiSize.h1 - me.stdout.tk.gocuiSize.h0
new1.Value = fmt.Sprintf("%d %d %d %d", width, height, me.stdout.tk.gocuiSize.w0, me.stdout.tk.gocuiSize.h0)
me.myTree.ConfigSave(new1)
// log.Log(NOW, "newWindowTrigger() gocui setting stdout size =", new1.Value)
// me.stdout.tk.dumpWidget("save")
}
}
// this code updates the clock
if time.Since(lastRefresh) > 1000*time.Millisecond {
// artificially pause clock while dragging.
// this is a reminder to make this refresh code smarter
// after the switch to protocol buffers
me.myTree.Lock()
if me.mouse.mouseUp {
// log.Info("refresh now on mouseUp")
// todo: add logic here to see if the application has changed anything
libNotifyUpdate()
lastRefresh = time.Now()
} else {
if time.Since(lastRefresh) > 3*time.Second {
libNotifyUpdate()
lastRefresh = time.Now()
}
}
me.myTree.Unlock()
}
}
}
// set the widget start width & height
func newWindowTrigger() {
// log.Log(NOW, "newWindowTriggerl() START")
for {
// log.Log(NOW, "GO plugin toolkit made a new window")
select {
case tk := <-me.newWindowTrigger:
// log.Log(NOW, "newWindowTrigger() got new window", tk.cuiName)
// time.Sleep(200 * time.Millisecond)
waitOK()
me.myTree.Lock()
// time.Sleep(200 * time.Millisecond)
redoWindows(me.FirstWindowW, me.FirstWindowH)
me.firstWindowOk = true
if !me.stdout.init {
me.stdout.init = true
relocateStdoutOffscreen()
}
if me.textbox.tk == nil {
initTextbox()
}
tk.makeWindowActive()
me.myTree.Unlock()
}
}
}

165
keybindings.go Normal file
View File

@ -0,0 +1,165 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
func defaultKeybindings(g *gocui.Gui) error {
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
return err
}
for _, n := range []string{"but1", "but2", "help", "but3"} {
if err := g.SetKeybinding(n, gocui.MouseLeft, gocui.ModNone, showMsg); err != nil {
return err
}
}
if err := g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, mouseUp); err != nil {
return err
}
// mouseDown() runs whenever you click on an unknown view (?)
if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, mouseDown); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModMouseCtrl, ctrlDown); err != nil {
return err
}
// if err := g.SetKeybinding(w.v.Name(), gocui.MouseLeft, gocui.ModNone, click); err != nil {
// return err
// }
/*
if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, globalDown); err != nil {
return err
}
*/
if err := g.SetKeybinding("msg", gocui.MouseLeft, gocui.ModNone, msgDown); err != nil {
return err
}
addDebugKeys(g)
return nil
}
func addDebugKeys(g *gocui.Gui) {
// show debugging buttons
g.SetKeybinding("", 'd', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
fakeStartWidth = me.FakeW
fakeStartHeight = me.TabH + me.FramePadH
if showDebug {
me.rootNode.showFake()
showDebug = false
} else {
me.rootNode.hideFake()
showDebug = true
}
return nil
})
// display the help menu
g.SetKeybinding("", '?', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
if showHelp {
helplayout()
showHelp = false
} else {
me.baseGui.DeleteView("help")
showHelp = true
}
return nil
})
// redraw all the widgets
g.SetKeybinding("", 'r', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
if redoWidgets {
redoWindows(0, 0)
redoWidgets = false
} else {
me.rootNode.hideWidgets()
redoWidgets = true
}
return nil
})
// hide all widgets
g.SetKeybinding("", 'h', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
me.rootNode.hideWidgets()
return nil
})
// show all widgets
g.SetKeybinding("", 's', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
me.rootNode.showWidgets()
return nil
})
// list all widgets
g.SetKeybinding("", 'L', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
me.rootNode.listWidgets()
return nil
})
// list all widgets with positions
g.SetKeybinding("", 'M', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
me.rootNode.dumpTree(true)
return nil
})
// log to output window
g.SetKeybinding("", 'o', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
log.Log(ERROR, "TODO: re-implement this")
if me.logStdout.Visible() {
me.logStdout.SetVisible(false)
// setOutput(os.Stdout)
} else {
me.logStdout.SetVisible(true)
// setOutput(me.logStdout.tk)
}
return nil
})
// exit
g.SetKeybinding("", 'q', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
standardExit()
return nil
})
g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
standardExit()
return nil
})
g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
if showDebug {
var a widget.Action
a.Value = true
a.ActionType = widget.EnableDebug
callback <- a
}
return nil
})
g.SetKeybinding("", gocui.KeyCtrlV, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
return nil
})
// panic
g.SetKeybinding("", 'p', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
standardExit()
panic("forced panic in gocui")
return nil
})
}

View File

@ -1,295 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// this file implements a libnotify-like menu
// also there is SIGWINCH resizing
package main
import (
"fmt"
"time"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
// create a new widget in the binary tree
func makeNewInternalWidget(wId int) *guiWidget {
if me.treeRoot == nil {
log.Info("GOGUI Init ERROR. treeRoot == nil")
return nil
}
n := new(tree.Node)
n.WidgetType = widget.Flag
n.WidgetId = wId
n.ParentId = 0
// store the internal toolkit information
tk := new(guiWidget)
tk.frame = true
tk.node = n
tk.node.Parent = me.treeRoot
// set the name used by gocui to the id
tk.cuiName = fmt.Sprintf("%d DR", wId)
tk.setColorInput()
// add this new widget on the binary tree
tk.parent = me.treeRoot.TK.(*guiWidget)
if tk.parent == nil {
panic("makeNewFlagWidget() didn't get treeRoot guiWidget")
} else {
tk.parent.children = append(tk.parent.children, tk)
}
n.TK = tk
return tk
}
func makeNotifyClock() {
if me.treeRoot == nil {
log.Info("gogui makeClock() error. treeRoot == nil")
return
}
me.notify.clock.tk = makeNewInternalWidget(me.notify.clock.wId)
// me.notify.clock.tk.dumpWidget("init() clock")
me.notify.clock.tk.MoveToOffset(0, 0)
me.notify.clock.tk.labelN = time.Now().Format("15:04:05")
me.notify.clock.tk.frame = false
me.notify.clock.tk.setColorLabel()
me.notify.clock.tk.Show()
me.notify.clock.active = true
// me.notify.clock.tk.dumpWidget("notifyClock()")
}
func makeNotifyIcon() {
if me.treeRoot == nil {
log.Info("gogui makeClock() error. treeRoot == nil")
return
}
me.notify.icon.tk = makeNewInternalWidget(me.notify.icon.wId)
// me.notify.icon.tk.dumpWidget("init() menu")
w, _ := me.baseGui.Size()
me.notify.icon.tk.MoveToOffset(w-5, me.notify.icon.offsetH)
me.notify.icon.tk.labelN = "[ ]"
me.notify.icon.tk.frame = false
me.notify.icon.tk.setColorNotifyIcon()
me.notify.icon.tk.Show()
me.notify.icon.active = true
// me.notify.icon.tk.dumpWidget("notifyIcon()")
}
func libNotifyUpdate() {
if me.baseGui == nil {
log.Info("libNotifyUpdate error baseGui == nil")
return
}
// refresh GOCUI
me.baseGui.Update(testRefresh)
// me.baseGui.UpdateAsync(testRefresh) // Async option. probably don't need this?
if me.notify.clock.tk == nil {
log.Info("libNotifyUpdate error clock.tk == nil")
return
}
// check for SIGWINCH. If so, move the libnotify clock
w, h := me.baseGui.Size()
if me.winchW != w || me.winchH != h {
if me.winchW == 0 && me.winchH == 0 {
// this isn't really SIGWINCH. This is the app starting
} else {
log.Printf("gocui: long live SIGWINCH! (w,h) is now (%d,%d)\n", w, h)
}
me.winchW = w
me.winchH = h
me.notify.clock.tk.MoveToOffset(w-me.notify.clock.offsetW, me.notify.clock.offsetH)
me.notify.clock.tk.Hide()
me.notify.clock.tk.Show()
sigWinchBG()
sigWinchIcon()
}
// update the time
me.notify.clock.tk.v.Clear()
me.notify.clock.tk.labelN = time.Now().Format("15:04:05")
me.notify.clock.tk.v.WriteString(me.notify.clock.tk.labelN)
hardDrawAtgocuiSize(me.notify.clock.tk)
// hardDrawUnderMouse(me.notify.clock.tk, "clock")
// log.Info("libNotifyUpdate updated clock", me.notify.clock.tk.labelN)
if me.notify.icon.tk == nil {
log.Info("libNotifyUpdate error menu.tk == nil")
return
}
if me.notify.icon.tk.v == nil {
log.Info("libNotifyUpdate error menu.tk.v == nil")
return
}
// update the menu
hardDrawAtgocuiSize(me.notify.icon.tk)
me.notify.icon.tk.setColorNotifyIcon()
me.baseGui.SetViewOnTop(me.notify.icon.tk.v.Name())
me.baseGui.SetViewOnTop(me.notify.clock.tk.v.Name())
}
func setNotifyIconText(s string) {
me.notify.icon.tk.v.Clear()
me.notify.icon.tk.labelN = s
me.notify.icon.tk.v.WriteString(me.notify.icon.tk.labelN)
hardDrawAtgocuiSize(me.notify.icon.tk)
me.notify.icon.tk.setColorNotifyIcon()
me.baseGui.SetViewOnTop(me.notify.icon.tk.v.Name())
log.Info("setNotifyIconText() updated menu to:", me.notify.icon.tk.labelN)
// print out the window list // TODO: put this in libnotify
for _, tk := range me.allwin {
log.Info("known window Window", tk.labelN, tk.window.active, tk.window.order)
}
if s == "[X]" {
log.Warn("should turn on help window here")
showHelp()
} else {
log.Warn("should turn off help window here")
hideHelp()
}
}
// in the very end of redrawing things, this will place the help and stdout on the top or botton
// depending on the state the user has chosen
func setThingsOnTop() {
if me.showHelp { // terrible variable name. FIXME
// log.Info("help does not exist")
} else {
me.baseGui.SetViewOnTop("help")
}
if me.notify.clock.tk != nil {
me.baseGui.SetViewOnTop(me.notify.clock.tk.v.Name())
}
if me.notify.icon.tk != nil {
if me.notify.icon.tk.v != nil {
me.baseGui.SetViewOnTop(me.notify.icon.tk.v.Name())
}
}
if me.stdout.tk == nil {
makeOutputWidget(me.baseGui, "from setThingsOnTop()")
}
if me.stdout.tk == nil {
return
}
if me.stdout.tk.v == nil {
return
}
if me.dark {
me.stdout.tk.v.FgColor = gocui.ColorWhite
me.stdout.tk.v.BgColor = gocui.ColorBlack
} else {
me.stdout.tk.v.FgColor = gocui.ColorBlack
me.stdout.tk.v.BgColor = gocui.AttrNone
}
if me.stdout.outputOnTop {
me.baseGui.SetViewOnTop("msg")
} else {
me.baseGui.SetViewOnBottom("msg")
}
if me.stdout.startOnscreen {
// log.Info("THIS TRIGGERS STDOUT") // todo: make a proper init() & move this there
me.stdout.tk.relocateStdout(me.stdout.lastW, me.stdout.lastH)
me.stdout.startOnscreen = false
}
setBottomBG()
}
// useful for debuggging
func hardDrawUnderMouse(tk *guiWidget, name string) {
if tk.v != nil {
tk.Hide()
}
w, h := me.baseGui.MousePosition()
r := new(rectType)
r.w0 = w
r.h0 = h
r.w1 = w + 8
r.h1 = h + 4
if err := tk.SetViewRect(r); err != nil {
log.Info("hardDrawUnderMouse() err", tk.cuiName, err)
tk.dumpWidget("hardDrawERR")
}
tk.v.Frame = false
tk.v.Clear()
tk.v.WriteString(tk.labelN + "\n" + name)
}
func hardDrawAtgocuiSize(tk *guiWidget) {
if tk.v != nil {
tk.Hide()
}
if err := tk.SetView(); err != nil {
log.Info("hardDrawAtgocuiSize() err ok widget", tk.cuiName)
tk.dumpWidget("hardDrawERR")
}
tk.v.Frame = false
tk.v.Clear()
tk.v.WriteString(tk.labelN)
// log.Verbose("hardDrawAtgocuiSize() err ok widget", tk.cuiName, a, b, c, d, tk.v.Name())
}
func sigWinchIcon() {
w, _ := me.baseGui.Size()
me.notify.icon.tk.MoveToOffset(w-me.notify.icon.offsetW, me.notify.icon.offsetH)
me.notify.icon.tk.Hide()
me.notify.icon.tk.Show()
}
func sigWinchBG() {
tk := me.BG.tk
w, h := me.baseGui.Size()
tk.gocuiSize.w0 = -1
tk.gocuiSize.h0 = -1
tk.gocuiSize.w1 = w + 1
tk.gocuiSize.h1 = h + 1
if err := tk.SetView(); err != nil {
tk.dumpWidget("sigWinchBGerr()")
log.Log(ERROR, "sigWinchBG()", err)
}
log.Log(INFO, "background resized to", tk.gocuiSize)
}
// find the "BG" widget and set it to the background on the very very bottom
func setBottomBG() {
if me.BG.tk == nil {
log.Info("background tk widget not initialized")
return
}
tk := me.BG.tk
// log.Info("found BG. setting to bottom", tk.cuiName)
if tk.v == nil {
sigWinchBG()
return
}
if me.dark {
tk.v.BgColor = gocui.ColorBlack
} else {
tk.v.BgColor = gocui.ColorWhite
}
tk.v.Clear()
me.baseGui.SetViewOnBottom(tk.cuiName)
w, h := me.baseGui.Size()
tk.gocuiSize.w0 = -1
tk.gocuiSize.h0 = -1
tk.gocuiSize.w1 = w + 1
tk.gocuiSize.h1 = h + 1
tk.SetView()
}

104
main.go Normal file
View File

@ -0,0 +1,104 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"os"
"go.wit.com/log"
"go.wit.com/widget"
)
// sets defaults and establishes communication
// to this toolkit from the wit/gui golang package
func init() {
log.Log(INFO, "Init() of awesome-gocui")
// init the config struct default values
Set(&me, "default")
pluginChan = make(chan widget.Action)
log.Log(NOW, "Init() start pluginChan")
go catchActionChannel()
log.Sleep(.1) // probably not needed, but in here for now under development
go main()
log.Sleep(.1) // probably not needed, but in here for now under development
}
/*
recieves requests from the program to do things like:
* add new widgets
* change the text of a label
* etc..
*/
func catchActionChannel() {
log.Log(INFO, "catchActionChannel() START")
for {
log.Log(INFO, "catchActionChannel() infinite for() loop restarted select on channel")
select {
case a := <-pluginChan:
if me.baseGui == nil {
// something went wrong initializing the gocui
log.Log(ERROR, "ERROR: console did not initialize")
continue
}
log.Log(INFO, "catchActionChannel()", a.WidgetId, a.ActionType, a.WidgetType, a.ProgName)
action(&a)
}
}
}
func Exit() {
// TODO: what should actually happen here?
log.Log(NOW, "Exit() here. doing standardExit()")
standardExit()
}
func standardExit() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
log.Log(NOW, "standardExit() doing outf.Close()")
outf.Close()
// log(true, "standardExit() setOutput(os.Stdout)")
// setOutput(os.Stdout)
log.Log(NOW, "standardExit() send back Quit()")
go sendBackQuit() // don't stall here in case the
// induces a delay in case the callback channel is broken
log.Sleep(1)
log.Log(NOW, "standardExit() exit()")
os.Exit(0)
}
func sendBackQuit() {
// send 'Quit' back to the program (?)
var a widget.Action
a.ActionType = widget.UserQuit
callback <- a
}
var outf *os.File
func main() {
var err error
log.Log(INFO, "main() start Init()")
outf, err = os.OpenFile("/tmp/witgui.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Error(err, "error opening file: %v")
os.Exit(0)
}
os.Stdout = outf
defer outf.Close()
// setOutput(outf)
// log("This is a test log entry")
ferr, _ := os.OpenFile("/tmp/witgui.err", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)
os.Stderr = ferr
gocuiMain()
log.Log(NOW, "MouseMain() closed")
standardExit()
}

151
mouse.go Normal file
View File

@ -0,0 +1,151 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
// this function uses the mouse position to highlight & unhighlight things
// this is run every time the user moves the mouse over the terminal window
func mouseMove(g *gocui.Gui) {
mx, my := g.MousePosition()
for _, view := range g.Views() {
view.Highlight = false
}
if v, err := g.ViewByPosition(mx, my); err == nil {
v.Highlight = true
}
}
func msgDown(g *gocui.Gui, v *gocui.View) error {
initialMouseX, initialMouseY = g.MousePosition()
log.Log(NOW, "msgDown() X,Y", initialMouseX, initialMouseY)
if vx, vy, _, _, err := g.ViewPosition("msg"); err == nil {
xOffset = initialMouseX - vx
yOffset = initialMouseY - vy
msgMouseDown = true
}
return nil
}
func hideDDview() error {
w, h := me.baseGui.MousePosition()
log.Log(NOW, "hide dropdown menu() view msgMouseDown (w,h) =", w, h)
if me.ddview == nil {
return gocui.ErrUnknownView
}
if me.ddview.tk.v == nil {
return gocui.ErrUnknownView
}
me.ddview.SetVisible(false)
return nil
}
func showDDview() error {
w, h := me.baseGui.MousePosition()
log.Log(NOW, "show dropdown menu() view msgMouseDown (w,h) =", w, h)
if me.ddview == nil {
return gocui.ErrUnknownView
}
if me.ddview.tk.v == nil {
return gocui.ErrUnknownView
}
me.ddview.SetVisible(true)
return nil
}
func mouseUp(g *gocui.Gui, v *gocui.View) error {
w, h := g.MousePosition()
log.Log(NOW, "mouseUp() view msgMouseDown (check here for dropdown menu click) (w,h) =", w, h)
if me.ddClicked {
me.ddClicked = false
log.Log(NOW, "mouseUp() ddview is the thing that was clicked", w, h)
log.Log(NOW, "mouseUp() find out what the string is here", w, h, me.ddview.tk.gocuiSize.h1)
var newZone string = ""
if me.ddNode != nil {
value := h - me.ddview.tk.gocuiSize.h0 - 1
log.Log(NOW, "mouseUp() me.ddview.tk.gocuiSize.h1 =", me.ddview.tk.gocuiSize.h1)
log.Log(NOW, "mouseUp() me.ddNode.vals =", me.ddNode.vals)
valsLen := len(me.ddNode.vals)
log.Log(NOW, "mouseUp() value =", value, "valsLen =", valsLen)
log.Log(NOW, "mouseUp() me.ddNode.vals =", me.ddNode.vals)
if (value >= 0) && (value < valsLen) {
newZone = me.ddNode.vals[value]
log.Log(NOW, "mouseUp() value =", value, "newZone =", newZone)
}
}
hideDDview()
if newZone != "" {
if me.ddNode != nil {
me.ddNode.SetText(newZone)
me.ddNode.value = newZone
me.ddNode.doUserEvent()
}
}
return nil
}
/*
// if there is a drop down view active, treat it like a dialog box and close it
if (hideDDview() == nil) {
return nil
}
*/
if msgMouseDown {
msgMouseDown = false
if movingMsg {
movingMsg = false
return nil
} else {
g.DeleteView("msg")
}
} else if globalMouseDown {
globalMouseDown = false
g.DeleteView("globalDown")
}
return nil
}
func mouseDown(g *gocui.Gui, v *gocui.View) error {
mx, my := g.MousePosition()
if vx0, vy0, vx1, vy1, err := g.ViewPosition("msg"); err == nil {
if mx >= vx0 && mx <= vx1 && my >= vy0 && my <= vy1 {
return msgDown(g, v)
}
}
globalMouseDown = true
maxX, _ := g.Size()
test := findUnderMouse()
msg := fmt.Sprintf("Mouse really down at: %d,%d", mx, my) + "foobar"
if test == me.ddview {
if me.ddview.Visible() {
log.Log(NOW, "hide DDview() Mouse really down at:", mx, my)
hideDDview()
} else {
log.Log(NOW, "show DDview() Mouse really down at:", mx, my)
showDDview()
}
return nil
}
x := mx - len(msg)/2
if x < 0 {
x = 0
} else if x+len(msg)+1 > maxX-1 {
x = maxX - 1 - len(msg) - 1
}
log.Log(NOW, "mouseDown() about to write out message to 'globalDown' view. msg =", msg)
if v, err := g.SetView("globalDown", x, my-1, x+len(msg)+1, my+1, 0); err != nil {
if !errors.Is(err, gocui.ErrUnknownView) {
return err
}
v.WriteString(msg)
}
return nil
}

63
node.go
View File

@ -1,63 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"go.wit.com/widget"
)
func (tk *guiWidget) WidgetType() widget.WidgetType {
if tk.node == nil {
return widget.Label
}
return tk.node.WidgetType
}
func (tk *guiWidget) WidgetId() int {
return tk.node.WidgetId
}
func (tk *guiWidget) GetLabel() string {
return tk.node.GetLabel()
}
func (tk *guiWidget) IsEnabled() bool {
return tk.node.IsEnabled()
}
func (tk *guiWidget) Checked() bool {
return tk.node.State.Checked
}
func (tk *guiWidget) Hidden() bool {
if tk.node == nil {
return false
}
if tk.parent == nil {
return tk.node.Hidden()
}
if tk.parent.WidgetId() == 0 {
return tk.node.Hidden()
}
if tk.parent.Hidden() {
return true
}
return tk.node.Hidden()
}
func (tk *guiWidget) Direction() widget.Orientation {
return tk.node.State.Direction
}
func (tk *guiWidget) GridW() int {
return tk.node.State.AtW
}
func (tk *guiWidget) GridH() int {
return tk.node.State.AtH
}
func (tk *guiWidget) SetChecked(b bool) {
tk.node.State.Checked = b
}

350
place.go
View File

@ -1,6 +1,3 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
@ -10,238 +7,119 @@ import (
"go.wit.com/widget"
)
/*
gocui defines the offset like this:
func (n *node) placeBox(startW int, startH int) {
if n.WidgetType != widget.Box {
return
}
n.showWidgetPlacement(true, "boxS()")
width -> increases to the right
---------------------------------- hieght
| H = 1 | increases
| | |
| W = 1 W = 18 | |
| | v
| H = 5 | downwards
-------------------------------------
*/
newW := startW
newH := startH
for _, child := range n.children {
child.placeWidgets(newW, newH)
// n.showWidgetPlacement(logNow, "boxS()")
newR := child.realGocuiSize()
w := newR.w1 - newR.w0
h := newR.h1 - newR.h0
if n.direction == widget.Horizontal {
log.Log(NOW, "BOX IS HORIZONTAL", n.progname, "newWH()", newW, newH, "child()", w, h, child.progname)
// expand based on the child width
newW += w
} else {
log.Log(NOW, "BOX IS VERTICAL ", n.progname, "newWH()", newW, newH, "child()", w, h, child.progname)
// expand based on the child height
newH += h
}
}
// moves the gocui view to the W and H offset on the screen
func (tk *guiWidget) MoveToOffset(W, H int) {
tk.gocuiSetWH(W, H)
// just compute this every time?
// newR := n.realGocuiSize()
n.showWidgetPlacement(true, "boxE()")
}
// returns where the corner of widget starts (upper left)
func (tk *guiWidget) Position() (int, int) {
return tk.gocuiSize.w0, tk.gocuiSize.h0
}
func (w *guiWidget) placeBox(startW int, startH int) {
if w.WidgetType() != widget.Box {
func (n *node) placeWidgets(startW int, startH int) {
if n == nil {
return
}
if me.rootNode == nil {
return
}
w.full.w0 = startW
w.full.h0 = startH
newW := startW
newH := startH
for _, child := range w.children {
sizeW, sizeH := child.Size()
log.Log(INFO, "BOX START size(W,H) =", sizeW, sizeH, "new(W,H) =", newW, newH)
child.placeWidgets(newW, newH)
// re-get the Size (they should not have changed, but maybe they can?)
// TODO: figure this out or report that they did
sizeW, sizeH = child.Size()
if w.Direction() == widget.Vertical {
log.Log(INFO, "BOX IS VERTICAL ", w.String(), "newWH()", newW, newH, "child()", sizeW, sizeH, child.String())
// expand based on the child height
newH += sizeH
} else {
log.Log(INFO, "BOX IS HORIZONTAL", w.String(), "newWH()", newW, newH, "child()", sizeW, sizeH, child.String())
// expand based on the child width
newW += sizeW
}
log.Log(INFO, "BOX END size(W,H) =", sizeW, sizeH, "new(W,H) =", newW, newH)
}
}
func (tk *guiWidget) placeWidgets(startW int, startH int) (int, int) {
if tk == nil {
return 0, 0
}
if me.treeRoot == nil {
return 0, 0
}
if tk.Hidden() {
return 0, 0
}
tk.startW = startW
tk.startH = startH
switch tk.WidgetType() {
switch n.WidgetType {
case widget.Window:
tk.full.w0 = startW
tk.full.h0 = startH
startW += -2
startH += 3
for _, child := range tk.children {
child.placeWidgets(startW, startH)
sizeW, _ := child.Size()
startW += sizeW + 4 // add the width to move the next widget over
for _, child := range n.children {
child.placeWidgets(me.RawW, me.RawH)
return
}
return startW, startH
case widget.Tab:
for _, child := range n.children {
child.placeWidgets(me.RawW, me.RawH)
return
}
case widget.Grid:
// tk.dumpWidget(fmt.Sprintf("PlaceGridS(%d,%d)", startW, startH))
// if you reset the values here, grid horizontal stacking doesn't work anymore
// tk.widths = make(map[int]int) // how tall each row in the grid is
// tk.heights = make(map[int]int) // how wide each column in the grid is
newW, newH := tk.placeGrid(startW, startH)
tk.full.w0 = newW
tk.full.h0 = newH
tk.full.w1 = newW
tk.full.h1 = newH
// tk.dumpWidget(fmt.Sprintf("PlaceGridE(%d,%d)", newW, newH))
return newW, newH
n.placeGrid(startW, startH)
case widget.Box:
tk.placeBox(startW, startH)
tk.full.w0 = startW
tk.full.h0 = startH
tk.full.w1 = startW
tk.full.h1 = startH
// tk.dumpWidget(fmt.Sprintf("PlaceBox(%d,%d)", startW, startH))
return 0, 0
case widget.Stdout:
tk.setStdoutWH(startW, startH)
return tk.gocuiSize.Width(), tk.gocuiSize.Height()
n.placeBox(startW, startH)
case widget.Group:
// move the group to the parent's next location
tk.gocuiSetWH(startW, startH)
tk.full.w0 = startW
tk.full.h0 = startH
tk.full.w1 = startW
tk.full.h1 = startH
n.gocuiSetWH(startW, startH)
n.showWidgetPlacement(true, "group()")
newW := startW + me.GroupPadW
newH := startH + 1 // normal hight of the group label
var maxW int = 0
newH := startH + 3 // normal hight of the group label
// now move all the children aka: run place() on them
for _, child := range tk.children {
sizeW, sizeH := child.placeWidgets(newW, newH)
// newR := child.realGocuiSize()
for _, child := range n.children {
child.placeWidgets(newW, newH)
newR := child.realGocuiSize()
// w := newR.w1 - newR.w0
// h := newR.h1 - newR.h0
h := newR.h1 - newR.h0
// increment straight down
newH += sizeH + 1
if sizeW > maxW {
maxW = sizeW
}
log.Log(INFO, "REAL HEIGHT sizeW:", sizeW, "sizeH:", sizeH)
newH += h
}
newH = newH - startH
// tk.dumpWidget(fmt.Sprintf("PlaceGroup(%d,%d)", maxW, newH))
return maxW, newH
case widget.Button:
if tk.isDense() && tk.isInGrid() {
tk.frame = false
// tk.color = nil
// tk.defaultColor = nil
/*
if tk.IsEnabled() {
tk.setColorButtonDense()
} else {
tk.setColorDisable()
}
*/
// if tk.full.Height() > 0 {
tk.full.h1 = tk.full.h0
// }
}
tk.gocuiSetWH(startW, startH)
return tk.gocuiSize.Width(), tk.gocuiSize.Height()
default:
tk.gocuiSetWH(startW, startH)
return tk.gocuiSize.Width(), tk.gocuiSize.Height()
n.gocuiSetWH(startW, startH)
// n.moveTo(startW, startH)
}
return 0, 0
}
func (tk *guiWidget) isDense() bool {
if tk.node.InTable() {
return true
func (n *node) placeGrid(startW int, startH int) {
w := n.tk
n.showWidgetPlacement(true, "grid0:")
if n.WidgetType != widget.Grid {
return
}
return tk.isWindowDense()
}
func (tk *guiWidget) isWindowDense() bool {
if tk.WidgetType() == widget.Window {
return tk.window.dense
}
if tk.parent == nil {
return true
}
return tk.parent.isWindowDense()
}
func (tk *guiWidget) isInGrid() bool {
if tk.WidgetType() == widget.Grid {
return true
}
if tk.parent == nil {
return true
}
return tk.parent.isInGrid()
}
func (w *guiWidget) placeGrid(startW int, startH int) (int, int) {
// w.showWidgetPlacement("grid0:")
if w.WidgetType() != widget.Grid {
return 0, 0
}
w.full.w0 = startW
w.full.h0 = startH
// first compute the max sizes of the rows and columns
for _, child := range w.children {
childW, childH := child.placeWidgets(child.startW, child.startH)
for _, child := range n.children {
newR := child.realGocuiSize()
childW := newR.w1 - newR.w0
childH := newR.h1 - newR.h0
// set the child's realWidth, and grid offset
if w.widths[child.GridW()] < childW {
w.widths[child.GridW()] = childW
if w.widths[child.AtW] < childW {
w.widths[child.AtW] = childW
}
if w.heights[child.GridH()] < childH {
w.heights[child.GridH()] = childH
if w.heights[child.AtH] < childH {
w.heights[child.AtH] = childH
}
if child.isDense() {
if w.heights[child.GridH()] > 0 {
w.heights[child.GridH()] = 1
} else {
w.heights[child.GridH()] = 0
}
}
// child.showWidgetPlacement("grid: ")
log.Log(INFO, "placeGrid:", child.String(), "child()", childW, childH, "At()", child.GridW(), child.GridH())
// child.showWidgetPlacement(logInfo, "grid: ")
log.Log(INFO, "placeGrid:", child.progname, "child()", childW, childH, "At()", child.AtW, child.AtH)
}
var maxW int = 0
var maxH int = 0
// find the width and height offset of the grid for AtW,AtH
for _, child := range w.children {
// child.showWidgetPlacement("grid1:")
for _, child := range n.children {
child.showWidgetPlacement(true, "grid1:")
var totalW, totalH int
for i, w := range w.widths {
if i < child.GridW() {
if i < child.AtW {
totalW += w
}
}
for i, h := range w.heights {
if i < child.GridH() {
if i < child.AtH {
totalH += h
}
}
@ -250,32 +128,20 @@ func (w *guiWidget) placeGrid(startW int, startH int) (int, int) {
newW := startW + totalW
newH := startH + totalH
if totalW > maxW {
maxW = totalW
}
if totalH > maxH {
maxH = totalH
}
log.Log(INFO, "placeGrid:", child.String(), "new()", newW, newH, "At()", child.GridW(), child.GridH())
log.Log(INFO, "placeGrid:", child.progname, "new()", newW, newH, "At()", child.AtW, child.AtH)
child.placeWidgets(newW, newH)
// child.showWidgetPlacement("grid2:")
child.showWidgetPlacement(true, "grid2:")
}
// w.showWidgetPlacement("grid3:")
w.full.w1 = startW + maxW
w.full.h1 = startH + maxH
return maxW, maxH
n.showWidgetPlacement(true, "grid3:")
}
// computes the real, actual size of all the gocli objects in a widget
func (w *guiWidget) realGocuiSize() *rectType {
var f func(tk *guiWidget, r *rectType)
func (n *node) realGocuiSize() *rectType {
var f func(n *node, r *rectType)
newR := new(rectType)
outputW, outputH := w.Size()
// initialize the values to opposite
newR.w0 = outputW
newR.h0 = outputH
newR.w0 = 80
newR.h0 = 24
if me.baseGui != nil {
maxW, maxH := me.baseGui.Size()
newR.w0 = maxW
@ -285,9 +151,9 @@ func (w *guiWidget) realGocuiSize() *rectType {
newR.h1 = 0
// expand the rectangle to the biggest thing displayed
f = func(tk *guiWidget, r *rectType) {
newR := tk.gocuiSize
if !tk.isFake {
f = func(n *node, r *rectType) {
newR := n.tk.gocuiSize
if !n.tk.isFake {
if r.w0 > newR.w0 {
r.w0 = newR.w0
}
@ -301,21 +167,18 @@ func (w *guiWidget) realGocuiSize() *rectType {
r.h1 = newR.h1
}
}
for _, child := range tk.children {
for _, child := range n.children {
f(child, r)
}
}
f(w, newR)
f(n, newR)
return newR
}
/*
func textSize(n *tree.Node) (int, int) {
var tk *guiWidget
tk = n.TK.(*guiWidget)
func (n *node) textSize() (int, int) {
var width, height int
for _, s := range strings.Split(widget.GetString(tk.value), "\n") {
for _, s := range strings.Split(widget.GetString(n.value), "\n") {
if width < len(s) {
width = len(s)
}
@ -323,46 +186,3 @@ func textSize(n *tree.Node) (int, int) {
}
return width, height
}
*/
func (tk *guiWidget) gocuiSetWH(sizeW, sizeH int) {
w := len(tk.GetLabel())
lines := strings.Split(tk.GetLabel(), "\n")
h := len(lines)
if tk.Hidden() {
tk.gocuiSize.w0 = 0
tk.gocuiSize.h0 = 0
tk.gocuiSize.w1 = 0
tk.gocuiSize.h1 = 0
return
}
// tk := n.tk
if tk.isFake {
tk.gocuiSize.w0 = sizeW
tk.gocuiSize.h0 = sizeH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + me.FramePadW
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + me.FramePadH
return
}
if tk.frame {
tk.gocuiSize.w0 = sizeW
tk.gocuiSize.h0 = sizeH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + me.FramePadW
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + me.FramePadH
} else {
tk.gocuiSize.w0 = sizeW - 1
tk.gocuiSize.h0 = sizeH - 1
tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + 1
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + 1
}
}
func (tk *guiWidget) setStdoutWH(sizeW, sizeH int) {
tk.gocuiSize.w0 = sizeW
tk.gocuiSize.h0 = sizeH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + me.stdout.w
tk.gocuiSize.h1 = tk.gocuiSize.h0 + me.stdout.h
}

292
plugin.go
View File

@ -1,247 +1,117 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"github.com/awesome-gocui/gocui"
// if you include more than just this import
// then your plugin might be doing something un-ideal (just a guess from 2023/02/27)
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func newAdd(n *tree.Node) {
if n == nil {
log.Warn("Tree Error: Add() sent n == nil")
return
func action(a *widget.Action) {
log.Log(INFO, "action() START", a.WidgetId, a.ActionType, a.WidgetType, a.ProgName)
n := me.rootNode.findWidgetId(a.WidgetId)
var w *guiWidget
if n != nil {
w = n.tk
}
if n.TK != nil {
log.Log(INFO, "Tree Add() sent a widget we aleady seem to have")
// this is done to protect the plugin being 'refreshed' with the
// widget binary tree. TODO: find a way to keep them in sync
return
}
n.TK = initWidget(n)
if n.WidgetType == widget.Root {
me.treeRoot = n
}
addWidget(n)
/*
TODO: removed while refactoring tree
if w.enable {
// don't change the color
switch a.ActionType {
case widget.Add:
if w == nil {
n := addNode(a)
// w = n.tk
n.addWidget()
} else {
w = n.TK.(*guiWidget)
// this is done to protect the plugin being 'refreshed' with the
// widget binary tree. TODO: find a way to keep them in sync
log.Log(ERROR, "action() Add ignored for already defined widget",
a.WidgetId, a.ActionType, a.WidgetType, a.ProgName)
}
*/
w := n.TK.(*guiWidget)
w.Show()
me.refresh = true // testing code to see if refresh can work
}
// for gocui as a GUI plugin, SetTitle & SetLabel are identical to SetText
func setTitle(n *tree.Node, s string) {
setText(n, s)
me.refresh = true // testing code to see if refresh can work
}
func setLabel(n *tree.Node, s string) {
setText(n, s)
me.refresh = true // testing code to see if refresh can work
}
// setText() and addText() are simple. They take the event sent
// to the GO plugin from the application and lookup the plugin structure
// then pass that event to gocui. This is the transfer point
func setText(n *tree.Node, s string) {
if n == nil {
log.Warn("Tree Error: Add() sent n == nil")
return
}
if n.TK == nil {
log.Warn("Tree sent an action on a widget we didn't seem to have.")
return
}
w := n.TK.(*guiWidget)
w.SetText(s)
me.refresh = true // testing code to see if refresh can work
}
func addText(n *tree.Node, s string) {
if n == nil {
log.Warn("Tree Error: Add() sent n == nil")
return
}
if n.TK == nil {
log.Warn("Tree sent an action on a widget we didn't seem to have.")
return
}
w := n.TK.(*guiWidget)
w.AddText(s)
me.refresh = true // testing code to see if refresh can work
}
func (w *guiWidget) deleteGocuiViews() {
if w.v == nil {
// no gocui view to delete for this widget
} else {
me.baseGui.DeleteView(w.cuiName)
w.v = nil
}
for _, child := range w.children {
child.deleteGocuiViews()
}
}
func (w *guiWidget) deleteNode() {
p := w.parent
for i, child := range p.children {
log.Log(NOW, "parent has child:", i, child.cuiName, child.String())
if w == child {
log.Log(NOW, "Found child ==", i, child.cuiName, child.String())
log.Log(NOW, "Found n ==", i, w.cuiName, w.String())
p.children = append(p.children[:i], p.children[i+1:]...)
case widget.Show:
if widget.GetBool(a.Value) {
n.showView()
} else {
n.hideWidgets()
}
case widget.Set:
if a.WidgetType == widget.Flag {
log.Log(NOW, "TODO: set flag here", a.ActionType, a.WidgetType, a.ProgName)
log.Log(NOW, "TODO: n.WidgetType =", n.WidgetType, "n.progname =", a.ProgName)
} else {
if a.Value == nil {
log.Log(ERROR, "TODO: Set here. a == nil id =", a.WidgetId, "type =", a.WidgetType, "Name =", a.ProgName)
log.Log(ERROR, "TODO: Set here. id =", a.WidgetId, "n.progname =", n.progname)
} else {
n.Set(a.Value)
}
}
case widget.SetText:
n.SetText(widget.GetString(a.Value))
case widget.AddText:
n.AddText(widget.GetString(a.Value))
case widget.Move:
log.Log(NOW, "attempt to move() =", a.ActionType, a.WidgetType, a.ProgName)
case widget.ToolkitClose:
log.Log(NOW, "attempting to close the plugin and release stdout and stderr")
standardExit()
case widget.Enable:
if n.Visible() {
// widget was already shown
} else {
log.Log(INFO, "Setting Visable to true", a.ProgName)
n.SetVisible(true)
}
case widget.Disable:
if n.Visible() {
log.Log(INFO, "Setting Visable to false", a.ProgName)
n.SetVisible(false)
} else {
// widget was already hidden
}
default:
log.Log(ERROR, "action() ActionType =", a.ActionType, "WidgetType =", a.WidgetType, "Name =", a.ProgName)
}
for i, child := range p.children {
log.Log(NOW, "parent now has child:", i, child.cuiName, child.String())
}
w.deleteGocuiViews()
log.Log(INFO, "action() END")
}
func (w *guiWidget) AddText(text string) {
if w == nil {
func (n *node) AddText(text string) {
if n == nil {
log.Log(NOW, "widget is nil")
return
}
w.vals = append(w.vals, text)
for i, s := range w.vals {
log.Log(INFO, "AddText()", w.String(), i, s)
n.vals = append(n.vals, text)
for i, s := range n.vals {
log.Log(NOW, "AddText()", n.progname, i, s)
}
w.SetText(text)
n.SetText(text)
}
func (tk *guiWidget) SetText(text string) {
func (n *node) SetText(text string) {
var changed bool = false
if tk == nil {
if n == nil {
log.Log(NOW, "widget is nil")
return
}
if tk.labelN != text {
tk.labelN = text
if widget.GetString(n.value) != text {
n.value = text
changed = true
}
tk.node.State.Label = text
if !changed {
return
}
if tk.Visible() {
tk.textResize()
tk.Hide()
tk.Show()
if n.Visible() {
n.textResize()
n.deleteView()
n.showView()
}
}
func (tk *guiWidget) GetText() string {
if tk == nil {
log.Log(NOW, "widget is nil")
return ""
}
// deprecate this
if tk.labelN != "" {
return tk.labelN
}
if tk.node == nil {
// return gocui.view name?
return tk.cuiName
}
if tk.GetLabel() != "" {
return tk.GetLabel()
}
return ""
}
func (n *node) Set(val any) {
// w := n.tk
log.Log(INFO, "Set() value =", val)
// hack. use "textbox widget" to "disable" user events
func hideDisable() {
if me.textbox.tk == nil {
initTextbox()
}
me.textbox.tk.Hide()
me.textbox.tk.enable = false
me.textbox.tk.node.State.Enable = false
me.textbox.active = false
me.baseGui.SetCurrentView("help")
// me.baseGui.DeleteView(me.textbox.tk.cuiName)
// me.baseGui.DeleteView(me.textbox.tk.v.Name())
}
// hack. use "textbox widget" to "disable" user events
func showDisable() {
if me.textbox.tk == nil {
initTextbox()
me.textbox.tk.prepTextbox()
}
r := new(rectType)
r.w0 = 2
r.h0 = 1
r.w1 = r.w0 + 24
r.h1 = r.h0 + 2
me.textbox.tk.forceSizes(r)
me.textbox.tk.Show() // actually makes the gocui view. TODO: redo this
// log.Info("textbox should be shown")
// showTextbox("Running...")
// me.textbox.tk.dumpWidget("shown?")
me.textbox.tk.setColorModal()
me.textbox.tk.v.Clear()
me.textbox.tk.v.WriteString("Running...")
me.textbox.tk.v.Editable = true
me.textbox.tk.v.Wrap = true
me.textbox.tk.SetViewRect(r)
me.baseGui.SetCurrentView(me.textbox.tk.v.Name())
// bind the enter key to a function so we can close the textbox
me.baseGui.SetKeybinding(me.textbox.tk.v.Name(), gocui.KeyEnter, gocui.ModNone, theCloseTheTextbox)
me.textbox.active = true
}
func (tk *guiWidget) Disable() {
if tk == nil {
log.Log(NOW, "widget is nil")
return
}
switch tk.WidgetType() {
case widget.Box:
showDisable()
return
case widget.Button:
tk.setColorDisable()
return
default:
tk.dumpWidget("fixme: disable")
}
}
func (tk *guiWidget) Enable() {
if tk == nil {
log.Log(NOW, "widget is nil")
return
}
switch tk.WidgetType() {
case widget.Box:
hideDisable()
return
case widget.Button:
tk.restoreEnableColor()
return
default:
tk.dumpWidget("fixme: enable")
n.value = val
if n.WidgetType != widget.Checkbox {
n.setCheckbox(val)
}
}

98
showStdout.go Normal file
View File

@ -0,0 +1,98 @@
package main
import (
"errors"
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
var outputW int = 180
var outputH int = 24
func moveMsg(g *gocui.Gui) {
mx, my := g.MousePosition()
if !movingMsg && (mx != initialMouseX || my != initialMouseY) {
movingMsg = true
}
g.SetView("msg", mx-xOffset, my-yOffset, mx-xOffset+outputW, my-yOffset+outputH+me.FramePadH, 0)
g.SetViewOnBottom("msg")
}
func showMsg(g *gocui.Gui, v *gocui.View) error {
var l string
var err error
log.Log(NOW, "showMsg() v.name =", v.Name())
if _, err := g.SetCurrentView(v.Name()); err != nil {
return err
}
_, cy := v.Cursor()
if l, err = v.Line(cy); err != nil {
l = ""
}
makeOutputWidget(g, l)
return nil
}
func makeOutputWidget(g *gocui.Gui, stringFromMouseClick string) *gocui.View {
maxX, maxY := g.Size()
if me.rootNode == nil {
// keep skipping this until the binary tree is initialized
return nil
}
if me.logStdout == nil {
a := new(widget.Action)
a.ProgName = "stdout"
a.WidgetType = widget.Stdout
a.WidgetId = -3
a.ParentId = 0
n := addNode(a)
me.logStdout = n
me.logStdout.tk.gocuiSize.w0 = maxX - 32
me.logStdout.tk.gocuiSize.h0 = maxY / 2
me.logStdout.tk.gocuiSize.w1 = me.logStdout.tk.gocuiSize.w0 + outputW
me.logStdout.tk.gocuiSize.h1 = me.logStdout.tk.gocuiSize.h0 + outputH
}
v, err := g.View("msg")
if v == nil {
log.Log(NOW, "makeoutputwindow() this is supposed to happen. v == nil", err)
} else {
log.Log(NOW, "makeoutputwindow() msg != nil. WTF now? err =", err)
}
// help, err := g.SetView("help", maxX-32, 0, maxX-1, 13, 0)
// v, err = g.SetView("msg", 3, 3, 30, 30, 0)
v, err = g.SetView("msg", maxX-32, maxY/2, maxX/2+outputW, maxY/2+outputH, 0)
if errors.Is(err, gocui.ErrUnknownView) {
log.Log(NOW, "makeoutputwindow() this is supposed to happen?", err)
}
if err != nil {
log.Log(NOW, "makeoutputwindow() create output window failed", err)
return nil
}
if v == nil {
log.Log(NOW, "makeoutputwindow() msg == nil. WTF now? err =", err)
return nil
} else {
me.logStdout.tk.v = v
}
v.Clear()
v.SelBgColor = gocui.ColorCyan
v.SelFgColor = gocui.ColorBlack
fmt.Fprintln(v, "figure out how to capture STDOUT to here\n"+stringFromMouseClick)
g.SetViewOnBottom("msg")
// g.SetViewOnBottom(v.Name())
return v
}

408
size.go
View File

@ -1,408 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"go.wit.com/widget"
)
func (tk *guiWidget) Size() (int, int) {
if tk == nil {
return 0, 0
}
if me.treeRoot == nil {
return 0, 0
}
// don't count hidden widgets in size calculations
if tk.Hidden() {
return 0, 0
}
switch tk.WidgetType() {
case widget.Window:
var maxH int = 0
var maxW int = 0
for _, child := range tk.children {
if tk.Hidden() {
continue
}
sizeW, sizeH := child.Size()
maxW += sizeW
if sizeH > maxH {
maxH = sizeH
}
}
return maxW, maxH
case widget.Grid:
return tk.sizeGrid()
case widget.Box:
return tk.sizeBox()
case widget.Group:
// move the group to the parent's next location
maxW := tk.gocuiSize.Width()
maxH := tk.gocuiSize.Height()
for _, child := range tk.children {
if tk.Hidden() {
continue
}
sizeW, sizeH := child.Size()
// increment straight down
maxH += sizeH
if sizeW > maxW {
maxW = sizeW
}
}
return maxW + me.GroupPadW + 3, maxH
case widget.Label:
return len(tk.String()) + 2, 1
case widget.Textbox:
return len(tk.String()) + 10, 3 // TODO: compute this based on 'window dense'
case widget.Checkbox:
return len(tk.String()) + 2, 3 // TODO: compute this based on 'window dense'
case widget.Button:
if tk.isDense() {
return len(tk.String()) + 2, 0
}
return len(tk.String()) + 2, 3 // TODO: compute this based on 'window dense'
}
if tk.isFake {
return 0, 0
}
return len(tk.String()), 3
}
func (w *guiWidget) sizeGrid() (int, int) {
if w.Hidden() {
return 0, 0
}
// first compute the max sizes of the rows and columns
for _, child := range w.children {
if w.Hidden() {
continue
}
sizeW, sizeH := child.Size()
// set the child's realWidth, and grid offset
if w.widths[child.GridW()] < sizeW {
w.widths[child.GridW()] = sizeW
}
if w.heights[child.GridH()] < sizeH {
w.heights[child.GridH()] = sizeH
}
}
// find the width and height offset of the grid for AtW,AtH
var totalW int = 0
var totalH int = 0
for _, width := range w.widths {
totalW += width
}
for _, h := range w.heights {
totalH += h
}
return totalW + me.GridPadW, totalH
}
func (w *guiWidget) sizeBox() (int, int) {
if w.WidgetType() != widget.Box {
return 0, 0
}
if w.Hidden() {
return 0, 0
}
var maxW int = 0
var maxH int = 0
for _, child := range w.children {
if w.Hidden() {
continue
}
sizeW, sizeH := child.Size()
if child.Direction() == widget.Vertical {
maxW += sizeW
if sizeH > maxH {
maxH = sizeH
}
} else {
maxH += sizeH
if sizeW > maxW {
maxW = sizeW
}
}
}
return maxW + me.BoxPadW, maxH
}
/*
var wtf bool
func (tk *guiWidget) verifyRect() bool {
if !tk.Visible() {
// log.Info("verifyRect() tk is not visible", tk.cuiName)
return false
}
vw0, vh0, vw1, vh1, err := me.baseGui.ViewPosition(tk.cuiName)
if err != nil {
// log.Printf("verifyRect() gocui err=%v cuiName=%s v.Name=%s", err, tk.cuiName, tk.v.Name())
vw0, vh0, vw1, vh1, err = me.baseGui.ViewPosition(tk.v.Name())
if err != nil {
log.Printf("verifyRect() ACTUAL FAIL gocui err=%v cuiName=%s v.Name=%s", err, tk.cuiName, tk.v.Name())
return false
}
// return false
}
var ok bool = true
if vw0 != tk.full.w0 {
// log.Info("verifyRect() FIXING w0", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.w0, "w0 =", vw0)
tk.full.w0 = vw0
ok = false
}
if vw1 != tk.full.w1 {
// log.Info("verifyRect() FIXING w1", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.w1, "w1 =", vw1)
tk.full.w1 = vw1
ok = false
}
if vh0 != tk.full.h0 {
// log.Info("verifyRect() FIXING h0", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.h0)
tk.full.h0 = vh0
ok = false
}
if vh1 != tk.full.h1 {
// log.Info("verifyRect() FIXING h1", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.h1)
tk.full.h1 = vh1
ok = false
}
if !ok {
// log.Info("verifyRect() NEED TO FIX RECT HERE", tk.cuiName)
// tk.dumpWidget("verifyRect() FIXME")
}
// log.Printf("verifyRect() OK cuiName=%s v.Name=%s", tk.cuiName, tk.v.Name())
return true
}
*/
func (tk *guiWidget) setFullSize() bool {
r := tk.getFullSize()
if tk.Hidden() {
p := tk.parent
if p != nil {
// tk.full.w0 = p.full.w0
// tk.full.w1 = p.full.w1
// tk.full.h0 = p.full.h0
// tk.full.h1 = p.full.h1
tk.full.w0 = 0
tk.full.w1 = 0
tk.full.h0 = 0
tk.full.h1 = 0
} else {
tk.full.w0 = 0
tk.full.w1 = 0
tk.full.h0 = 0
tk.full.h1 = 0
}
return false
}
var changed bool
if tk.full.w0 != r.w0 {
tk.full.w0 = r.w0
changed = true
}
// widget might be forced to a certain location
if tk.full.w0 < tk.force.w0 {
tk.gocuiSize.w0 = tk.force.w0
tk.full.w0 = tk.force.w0
changed = false
}
if tk.full.w1 != r.w1 {
tk.full.w1 = r.w1
changed = true
}
if tk.full.h0 != r.h0 {
tk.full.h0 = r.h0
changed = true
}
// widget might be forced to a certain location
if tk.full.h0 < tk.force.h0 {
tk.gocuiSize.h0 = tk.force.h0
tk.full.h0 = tk.force.h0
changed = false
}
if tk.full.h1 != r.h1 {
tk.full.h1 = r.h1
changed = true
}
if tk.WidgetType() == widget.Button {
tk.full.h1 = tk.full.h0 + 1
}
if tk.isDense() && tk.isInGrid() {
tk.full.h1 = tk.full.h0
}
if changed {
tk.dumpWidget(fmt.Sprintf("setFullSize(changed)"))
}
return changed
}
func (tk *guiWidget) gridFullSize() rectType {
var r rectType
var first bool = true
for _, child := range tk.children {
cr := child.getFullSize()
if cr.Width() == 0 && cr.Height() == 0 {
// ignore widgets of zero size?
continue
}
if first {
// use the lowest width and hight from children widgets
r.w0 = cr.w0
r.h0 = cr.h0
r.w1 = cr.w1
r.h1 = cr.h1
first = false
// child.dumpWidget(fmt.Sprintf("grid(f)"))
continue
}
// child.dumpWidget(fmt.Sprintf("grid()"))
// use the lowest width and hight from children widgets
if r.w0 > cr.w0 {
r.w0 = cr.w0
}
if r.h0 > cr.h0 {
r.h0 = cr.h0
}
// use the highest width and hight from children widgets
if r.w1 < cr.w1 {
r.w1 = cr.w1
}
if r.h1 < cr.h1 {
r.h1 = cr.h1
}
}
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
return r
}
func (tk *guiWidget) buttonFullSize() rectType {
var r rectType
r.w0 = tk.gocuiSize.w0
r.w1 = tk.gocuiSize.w1
r.h0 = tk.gocuiSize.h0
r.h1 = tk.gocuiSize.h1
// try setting the full values here ? is this right?
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
// total hack. fix this somewhere eventually correctly
if tk.isDense() { // total hack. fix this somewhere eventually correctly
tk.full.h0 += 1 // total hack. fix this somewhere eventually correctly
tk.full.h1 = tk.full.h0 // total hack. fix this somewhere eventually correctly
}
return r
}
// this checks a widget to see if it is under (W,H), then checks the widget's children
// anything that matches is passed back as an array of widgets
func (tk *guiWidget) getFullSize() rectType {
var r rectType
if tk.Hidden() {
/*
p := tk.parent
if p != nil {
return p.full
}
*/
var r rectType
r.w0 = 0
r.w1 = 0
r.h0 = 0
r.h1 = 0
return r
}
if tk.WidgetType() == widget.Grid {
return tk.gridFullSize()
}
// these are 'simple' widgets
// the full size is exactly what gocui uses
switch tk.WidgetType() {
case widget.Label:
r := tk.buttonFullSize()
r.w1 += 5
return r
case widget.Button:
r := tk.buttonFullSize()
r.w1 += 5
return r
case widget.Checkbox:
return tk.buttonFullSize()
case widget.Dropdown:
r := tk.buttonFullSize()
r.w1 += 7 // TODO: fix this to be real
return r
default:
}
if tk.v == nil {
r.w0 = tk.full.w0
r.w1 = tk.full.w1
r.h0 = tk.full.h0
r.h1 = tk.full.h1
} else {
r.w0 = tk.gocuiSize.w0
r.w1 = tk.gocuiSize.w1
r.h0 = tk.gocuiSize.h0
r.h1 = tk.gocuiSize.h1
}
// search through the children widgets in the binary tree
for _, child := range tk.children {
cr := child.getFullSize()
/* this didn't make things work either
if child.v == nil {
continue
}
*/
// use the lowest width and hight from children widgets
if r.w0 > cr.w0 {
r.w0 = cr.w0
}
if r.h0 > cr.h0 {
r.h0 = cr.h0
}
// use the highest width and hight from children widgets
if r.w1 < cr.w1 {
r.w1 = cr.w1
}
if r.h1 < cr.h1 {
r.h1 = cr.h1
}
}
// try setting the full values here ? is this right?
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
return r
}

View File

@ -1,219 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"slices"
"strings"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
func createStdout(g *gocui.Gui) bool {
if me.stdout.tk == nil {
makeOutputWidget(g, "this is a create before a mouse click")
// me.logStdout.v.Write([]byte(msg))
// this will show very early debugging output
// keep this code commented out but do not remove it. when it doubt, this will be the Light of Elendil
// NEVER REMOVE THIS CODE
msg := fmt.Sprintf("test out gocuiEvent() %d\n", me.ecount)
me.stdout.tk.Write([]byte(msg))
log.Log(NOW, "logStdout test out")
}
return true
}
func coreStdout() {
if me.stdout.tk != nil {
return
}
a := new(widget.Action)
a.ProgName = "2stdout2"
a.WidgetType = widget.Stdout
a.WidgetId = me.stdout.wId
a.ParentId = 0
// n := addNode(a)
n := me.myTree.AddNode(a)
me.stdout.tk = initWidget(n)
tk := me.stdout.tk
tk.cuiName = "msg"
tk.gocuiSize.w0 = me.stdout.lastW
tk.gocuiSize.h0 = me.stdout.lastH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + me.stdout.w
tk.gocuiSize.h1 = tk.gocuiSize.h0 + me.stdout.h
}
func makeOutputWidget(g *gocui.Gui, stringFromMouseClick string) *gocui.View {
if me.treeRoot == nil {
// keep skipping this until the binary tree is initialized
return nil
}
coreStdout()
if me.stdout.tk == nil {
return nil
}
me.stdout.tk.cuiName = "msg"
me.stdout.tk.SetView()
v, err := g.View("msg")
if v == nil {
// log.Log(NOW, "makeoutputwindow() this is supposed to happen. v == nil", err)
} else {
log.Log(NOW, "makeoutputwindow() msg != nil. WTF now? err =", err)
return v
}
v = me.stdout.tk.v
v.Clear()
v.SelBgColor = gocui.ColorCyan
v.SelFgColor = gocui.ColorBlack
fmt.Fprintln(v, "figure out how to capture STDOUT to here\n"+stringFromMouseClick)
// g.SetViewOnBottom("msg")
// setBottomBG()
me.stdout.tk.DrawAt(me.stdout.lastW, me.stdout.lastH)
relocateStdoutOffscreen()
return v
}
func relocateStdoutOffscreen() {
if me.stdout.tk == nil {
return
}
if !me.stdout.disable {
log.Info("Using gocui STDOUT")
log.CaptureMode(me.stdout.tk)
}
newW := 10
newH := 0 - me.stdout.h - 4
me.stdout.tk.relocateStdout(newW, newH)
}
func (tk *guiWidget) relocateStdout(w int, h int) {
if me.stdout.w < 8 {
me.stdout.w = 8
}
if me.stdout.h < 4 {
me.stdout.h = 4
}
if w+me.stdout.w < 2 {
w = 2
}
if h+me.stdout.h < 2 {
h = 2
}
w0 := w
h0 := h
w1 := w + me.stdout.w
h1 := h + me.stdout.h
tk.gocuiSize.w0 = w0
tk.gocuiSize.w1 = w1
tk.gocuiSize.h0 = h0
tk.gocuiSize.h1 = h1
tk.full.w0 = w0
tk.full.w1 = w1
tk.full.h0 = h0
tk.full.h1 = h1
tk.SetView()
}
// from the gocui devs:
// Write appends a byte slice into the view's internal buffer. Because
// View implements the io.Writer interface, it can be passed as parameter
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
// be called to clear the view's buffer.
func (w stdout) Height() int {
if w.tk == nil {
return 40
}
return w.tk.gocuiSize.Height() - 2
}
func (w stdout) Write(p []byte) (n int, err error) {
me.writeMutex.Lock()
defer me.writeMutex.Unlock()
lines := strings.Split(strings.TrimSpace(string(p)), "\n")
me.stdout.outputS = append(me.stdout.outputS, lines...)
if me.outf != nil {
fmt.Fprint(me.outf, string(p))
}
return len(p), nil
}
func (w *guiWidget) Write(p []byte) (n int, err error) {
lines := strings.Split(strings.TrimSpace(string(p)), "\n")
if me.outf != nil {
fmt.Fprint(me.outf, string(p))
}
if w == nil {
me.stdout.outputS = append(me.stdout.outputS, lines...)
return len(p), nil
}
w.tainted = true
me.writeMutex.Lock()
defer me.writeMutex.Unlock()
me.stdout.outputS = append(me.stdout.outputS, lines...)
tk := me.stdout.tk
if tk == nil {
return len(p), nil
}
if tk.v == nil {
// redo this old code
v, _ := me.baseGui.View("msg")
if v != nil {
tk.v = v
}
return len(p), nil
}
tk.refreshStdout()
return len(p), nil
}
// lets the user page up and down through the stdout buffer
func (tk *guiWidget) refreshStdout() {
if len(me.stdout.outputS) < me.stdout.h+me.stdout.pager {
// log.Info(fmt.Sprintf("buffer too small=%d len(%d)", me.stdout.pager, len(me.stdout.outputS)))
var cur []string
cur = append(cur, me.stdout.outputS...)
slices.Reverse(cur)
tk.v.Clear()
fmt.Fprintln(tk.v, strings.Join(cur, "\n"))
return
}
var cur []string
// chop off the last lines in the buffer
chop := len(me.stdout.outputS) - (me.stdout.pager + me.stdout.h)
cur = append(cur, me.stdout.outputS[chop:chop+me.stdout.h]...)
if me.stdout.reverse {
slices.Reverse(cur)
}
tk.v.Clear()
fmt.Fprintln(tk.v, strings.Join(cur, "\n"))
}

View File

@ -1,5 +1,5 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// LICENSE: same as the go language itself
// Copyright 2023 WIT.COM
// all structures and variables are local (aka lowercase)
// since the plugin should be isolated to access only
@ -10,150 +10,94 @@ package main
import (
"fmt"
"os"
"github.com/awesome-gocui/gocui"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/awesome-gocui/gocui"
"go.wit.com/lib/protobuf/guipb"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
"go.wit.com/log"
)
var initOnce sync.Once // run initPlugin() only once
// It's probably a terrible idea to call this 'me'
// 2025 note: doesn't seem terrible to call this 'me' anymore. notsure.
var me config
// todo: move all this to a protobuf. then, redo all this mess.
// it got me here, but now it's time to clean it up for good
// I can't get a GO plugins that use protobuf to load yet (versioning mismatch)
var showDebug bool = true
var showHelp bool = true
var redoWidgets bool = true
// This is the window that is currently active
var currentWindow *node
type config struct {
baseGui *gocui.Gui // the main gocui handle
treeRoot *tree.Node // the base of the binary tree. it should have id == 0
myTree *tree.TreeInfo // ?
currentWindow *guiWidget // this is the current tab or window to show
ok bool // if the user doesn't hit a key or move the mouse, gocui doesn't really start
firstWindowOk bool // allows the init to wait for the first window from the application
refresh bool // redraw everything?
ctrlDown *tree.Node // shown if you click the mouse when the ctrl key is pressed
helpLabel *gocui.View // ?
showHelp bool // toggle boolean for the help menu (deprecate?)
FirstWindowW int `default:"2"` // how far over to start window #1
FirstWindowH int `default:"0"` // how far down to start window #1
FramePadW int `default:"1" dense:"0"` // When the widget has a frame, like a button, it adds 2 lines runes on each side
FramePadH int `default:"1" dense:"0"` // When the widget has a frame, like a button, it adds 2 lines runes on each side
PadW int `default:"1" dense:"0"` // pad spacing
PadH int `default:"1" dense:"0"` // pad spacing
WindowW int `default:"8" dense:"0"` // how far down to start Window or Tab headings
WindowH int `default:"-1"` // how far down to start Window or Tab headings
TabW int `default:"5" dense:"0"` // how far down to start Window or Tab headings
TabH int `default:"1" dense:"0"` // how far down to start Window or Tab headings
WindowPadW int `default:"8" dense:"0"` // additional amount of space to put between window & tab widgets
TabPadW int `default:"4" dense:"0"` // additional amount of space to put between window & tab widgets
GroupPadW int `default:"2" dense:"1"` // additional amount of space to indent on a group
BoxPadW int `default:"2" dense:"1"` // additional amount of space to indent on a box
GridPadW int `default:"2" dense:"1"` // additional amount of space to indent on a grid
RawW int `default:"1"` // the raw beginning of each window (or tab)
RawH int `default:"5"` // the raw beginning of each window (or tab)
FakeW int `default:"20"` // offset for the hidden widgets
DropdownId int `default:"-78"` // the widget id to use
padded bool // add space between things like buttons
bookshelf bool // do you want things arranged in the box like a bookshelf or a stack?
canvas bool // if set to true, the windows are a raw canvas
menubar bool // for windows
stretchy bool // expand things like buttons to the maximum size
margin bool // add space around the frames of windows
writeMutex sync.Mutex // writeMutex protects writes to *guiWidget (it's global right now maybe)
ecount int // counts how many mouse and keyboard events have occurred
supermouse bool // prints out every widget found while you move the mouse around
depth int // used for listWidgets() debugging
newWindowTrigger chan *guiWidget // work around hack to redraw windows a bit after NewWindow()
stdout stdout // information for the STDOUT window
dropdown dropdown // the dropdown menu
textbox dropdown // the textbox popup window
BG dropdown // the background widget
notify libnotify // emulates the desktop libnotify menu
allwin []*guiWidget // for tracking which window is next
dark bool // use a 'dark' color palette
mouse mouse // mouse settings
showDebug bool // todo: move this into config struct
debug bool // todo: move this into config struct
starttime time.Time // checks how long it takes on startup
winchW int // used to detect SIGWINCH
winchH int // used to detect SIGWINCH
outf *os.File // hacks for capturing stdout
baseGui *gocui.Gui // the main gocui handle
rootNode *node // the base of the binary tree. it should have id == 0
ctrlDown *node // shown if you click the mouse when the ctrl key is pressed
currentWindow *node // this is the current tab or window to show
logStdout *node // where to show STDOUT
helpLabel *gocui.View
ddview *node // the gocui view to select dropdrown lists
ddClicked bool // the dropdown menu view was clicked
ddNode *node // the dropdown menu is for this widget
/*
// this is the channel we send user events like
// mouse clicks or keyboard events back to the program
callback chan toolkit.Action
// this is the channel we get requests to make widgets
pluginChan chan toolkit.Action
*/
// When the widget has a frame, like a button, it adds 2 lines runes on each side
// so you need 3 char spacing in each direction to not have them overlap
// the amount of padding when there is a frame
FramePadW int `default:"1" dense:"0"`
FramePadH int `default:"1" dense:"0"`
PadW int `default:"1" dense:"0"`
PadH int `default:"1" dense:"0"`
// how far down to start Window or Tab headings
WindowW int `default:"8" dense:"0"`
WindowH int `default:"-1"`
TabW int `default:"5" dense:"0"`
TabH int `default:"1" dense:"0"`
// additional amount of space to put between window & tab widgets
WindowPadW int `default:"8" dense:"0"`
TabPadW int `default:"4" dense:"0"`
// additional amount of space to indent on a group
GroupPadW int `default:"6" dense:"2"`
// the raw beginning of each window (or tab)
RawW int `default:"1"`
RawH int `default:"5"`
// offset for the hidden widgets
FakeW int `default:"20"`
padded bool // add space between things like buttons
bookshelf bool // do you want things arranged in the box like a bookshelf or a stack?
canvas bool // if set to true, the windows are a raw canvas
menubar bool // for windows
stretchy bool // expand things like buttons to the maximum size
margin bool // add space around the frames of windows
// writeMutex protects locks the write process
writeMutex sync.Mutex
// used for listWidgets() debugging
depth int
}
// stuff controlling how the mouse works
type mouse struct {
down time.Time // when the mouse was pressed down
up time.Time // when the mouse was released. used to detect click vs drag
clicktime time.Duration // how long is too long for a mouse click vs drag
mouseUp bool // is the mouse up?
double bool // user is double clicking
doubletime time.Duration // how long is too long for double click
resize bool // mouse is resizing something
downW int // where the mouse was pressed down
downH int // where the mouse was pressed down
currentDrag *guiWidget // what widget is currently being moved around
}
// settings for the stdout window
type stdout struct {
tk *guiWidget // where to show STDOUT
wId int // the widget id
w int // the width
h int // the height
outputOnTop bool // is the STDOUT window on top?
outputOffscreen bool // is the STDOUT window offscreen?
startOnscreen bool // start the output window onscreen?
disable bool // disable the stdout window. do not change os.Stdout & os.Stderr
lastW int // the last 'w' location (used to move from offscreen to onscreen)
lastH int // the last 'h' location (used to move from offscreen to onscreen)
init bool // moves the window offscreen on startup
outputS []string // the buffer of all the output
pager int // allows the user to page through the buffer
changed bool // indicates the user has changed stdout. gocui should remember the state here
reverse bool // flip the STDOUT upside down so new STDOUT lines are at the top
}
// settings for the dropdown window
type dropdown struct {
tk *guiWidget // where to show STDOUT
callerTK *guiWidget // which widget called the dropdown menu
items []string // what is currently in the menu
w int // the width
h int // the height
active bool // is the dropdown menu currently in use?
init bool // moves the window offscreen on startup
// Id int `default:"-78"` // the widget id to use
wId int `default:"-78"` // the widget id to use
}
// settings for the dropdown window
type internalTK struct {
once sync.Once // for init
tk *guiWidget // where to show STDOUT
callerTK *guiWidget // which widget called the dropdown menu
wId int // the widget id to use
active bool // is the internal widget currently in use?
offsetW int // width offset
offsetH int // height offset
}
// the desktop libnotify menu
type libnotify struct {
clock internalTK // widget for the clock
icon internalTK // libnotify menu icon
window internalTK // the libnotify menu
help internalTK // the help menu
}
// deprecate these
var (
initialMouseX, initialMouseY, xOffset, yOffset int
globalMouseDown, msgMouseDown, movingMsg bool
)
// this is the gocui way
// corner starts at in the upper left corner
@ -166,87 +110,80 @@ func (r *rectType) Width() int {
}
func (r *rectType) Height() int {
if r.h0 == 0 && r.h1 == 0 {
// edge case. only return 0 for these
return 0
}
if r.h1 == r.h0 {
// if they are equal, it's actually height = 1
return 1
}
if r.h1-r.h0 < 1 {
// can't have negatives. something is wrong. return 1 for now
return 1
}
return r.h1 - r.h0
}
// settings that are window specific
type window struct {
windowFrame *guiWidget // this is the frame for a window widget
wasDragged bool // indicates the window was dragged. This keeps it from being rearranged
hasTabs bool // does the window have tabs?
currentTab bool // the visible tab
selectedTab *tree.Node // for a window, this is currently selected tab
active bool // means this window is the active one
order int // what level the window is on
// resize bool // only set the title once
collapsed bool // only show the window title bar
dense bool // true if the window is dense
large bool // true if the window is huge
pager int // allows the user to page through the window
}
type colorT struct {
frame gocui.Attribute
fg gocui.Attribute
bg gocui.Attribute
selFg gocui.Attribute
selBg gocui.Attribute
name string
}
type guiWidget struct {
v *gocui.View // this is nil if the widget is not displayed
cuiName string // what gocui uses to reference the widget (usually "TK <widgetId>"
parent *guiWidget // mirrors the binary node tree
children []*guiWidget // mirrors the binary node tree
node *tree.Node // the pointer back to the tree
pb *guipb.Widget // the guipb Widget
wtype widget.WidgetType // used for Tables for now. todo: fix this correctly
windowFrame *guiWidget // this is the frame for a window widget
internal bool // indicates the widget is internal to gocui and should be treated differently
hasTabs bool // does the window have tabs?
currentTab bool // the visible tab
window window // holds information specific only to Window widgets
value string // ?
checked bool // ?
labelN string // the actual text to display in the console
vals []string // dropdown menu items
enable bool // ?
gocuiSize rectType // should mirror the real display size. todo: verify these are accurate. they are not yet
full rectType // full size of children (used by widget.Window, etc)
force rectType // force widget within these boundries (using this to debug window dragging)
startW int // ?
startH int // ?
lastW int // used during mouse dragging
lastH int // used during mouse dragging
isFake bool // widget types like 'box' are 'false'
widths map[int]int // how tall each row in the grid is
heights map[int]int // how wide each column in the grid is
tainted bool // ?
frame bool // ?
selectedTab *tree.Node // for a window, this is currently selected tab
color *colorT // what color to use
colorLast colorT // the last color the widget had
defaultColor *colorT // the default colors // TODO: make a function for this instead
isTable bool // is this a table?
// the gocui package variables
v *gocui.View // this is nil if the widget is not displayed
cuiName string // what gocui uses to reference the widget
// the actual text to display in the console
label string
// the logical size of the widget
// For example, 40x12 would be the center of a normal terminal
// size rectType
// the actual gocui display view of this widget
// sometimes this isn't visible like with a Box or Grid
gocuiSize rectType
isCurrent bool // is this the currently displayed Window or Tab?
isFake bool // widget types like 'box' are 'false'
// used to track the size of grids
widths map[int]int // how tall each row in the grid is
heights map[int]int // how wide each column in the grid is
tainted bool
frame bool
// for a window, this is currently selected tab
selectedTab *node
// what color to use
color *colorT
}
// from the gocui devs:
// Write appends a byte slice into the view's internal buffer. Because
// View implements the io.Writer interface, it can be passed as parameter
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
// be called to clear the view's buffer.
func (w *guiWidget) Write(p []byte) (n int, err error) {
w.tainted = true
me.writeMutex.Lock()
defer me.writeMutex.Unlock()
if me.logStdout.tk.v == nil {
// optionally write the output to /tmp
s := fmt.Sprint(string(p))
s = strings.TrimSuffix(s, "\n")
fmt.Fprintln(outf, s)
v, _ := me.baseGui.View("msg")
if v != nil {
// fmt.Fprintln(outf, "found msg")
me.logStdout.tk.v = v
}
} else {
// display the output in the gocui window
me.logStdout.tk.v.Clear()
s := fmt.Sprint(string(p))
s = strings.TrimSuffix(s, "\n")
tmp := strings.Split(s, "\n")
outputS = append(outputS, tmp...)
if len(outputS) > outputH {
l := len(outputS) - outputH
outputS = outputS[l:]
}
fmt.Fprintln(me.logStdout.tk.v, strings.Join(outputS, "\n"))
}
return len(p), nil
}
// THIS IS GO COMPILER MAGIC
// this sets the `default` in the structs above on init()
// this is cool code. thank the GO devs for this code and Alex Flint for explaining it to me
func Set(ptr interface{}, tag string) error {
if reflect.TypeOf(ptr).Kind() != reflect.Ptr {
log.Log(ERROR, "Set() Not a pointer", ptr, "with tag =", tag)
@ -266,11 +203,12 @@ func Set(ptr interface{}, tag string) error {
}
func setField(field reflect.Value, defaultVal string, name string) error {
if !field.CanSet() {
// log("setField() Can't set value", field, defaultVal)
return fmt.Errorf("Can't set value\n")
} else {
// log.Log(NOW, "setField() Can set value", name, defaultVal)
log.Log(NOW, "setField() Can set value", name, defaultVal)
}
switch field.Kind() {

111
tab.go Normal file
View File

@ -0,0 +1,111 @@
package main
// implements widgets 'Window' and 'Tab'
import (
"strings"
"go.wit.com/log"
"go.wit.com/widget"
)
func (w *guiWidget) Width() int {
if w.frame {
return w.gocuiSize.w1 - w.gocuiSize.w0
}
return w.gocuiSize.w1 - w.gocuiSize.w0 - 1
}
func (w *guiWidget) Height() int {
if w.frame {
return w.gocuiSize.h1 - w.gocuiSize.h0
}
return w.gocuiSize.h1 - w.gocuiSize.h0 - 1
}
func (n *node) gocuiSetWH(sizeW, sizeH int) {
w := len(widget.GetString(n.value))
lines := strings.Split(widget.GetString(n.value), "\n")
h := len(lines)
tk := n.tk
if tk.isFake {
tk.gocuiSize.w0 = sizeW
tk.gocuiSize.h0 = sizeH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + me.FramePadW
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + me.FramePadH
return
}
if tk.frame {
tk.gocuiSize.w0 = sizeW
tk.gocuiSize.h0 = sizeH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + me.FramePadW
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + me.FramePadH
} else {
tk.gocuiSize.w0 = sizeW - 1
tk.gocuiSize.h0 = sizeH - 1
tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + 1
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + 1
}
}
func redoWindows(nextW int, nextH int) {
for _, n := range me.rootNode.children {
if n.WidgetType != widget.Window {
continue
}
w := n.tk
var tabs bool
for _, child := range n.children {
if child.WidgetType == widget.Tab {
tabs = true
}
}
if tabs {
// window is tabs. Don't show it as a standard button
w.frame = false
n.hasTabs = true
} else {
w.frame = false
n.hasTabs = false
}
n.gocuiSetWH(nextW, nextH)
n.deleteView()
n.showView()
sizeW := w.Width() + me.WindowPadW
sizeH := w.Height()
nextW += sizeW
log.Log(NOW, "redoWindows() start nextW,H =", nextW, nextH, "gocuiSize.W,H =", sizeW, sizeH, n.progname)
if n.hasTabs {
n.redoTabs(me.TabW, me.TabH)
}
}
}
func (p *node) redoTabs(nextW int, nextH int) {
for _, n := range p.children {
if n.WidgetType != widget.Tab {
continue
}
w := n.tk
w.frame = true
n.gocuiSetWH(nextW, nextH)
n.deleteView()
// setCurrentTab(n)
// if (len(w.cuiName) < 4) {
// w.cuiName = "abcd"
// }
n.showView()
sizeW := w.Width() + me.TabPadW
sizeH := w.Height()
log.Log(NOW, "redoTabs() start nextW,H =", nextW, nextH, "gocuiSize.W,H =", sizeW, sizeH, n.progname)
nextW += sizeW
}
}

View File

@ -1,93 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"slices"
"go.wit.com/lib/protobuf/guipb"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func initGridPB(pb *guipb.Widget) *guiWidget {
var w *guiWidget
w = new(guiWidget)
w.pb = pb
w.wtype = widget.Grid
w.cuiName = fmt.Sprintf("%d %s", pb.Id, "TK")
w.labelN = pb.Name
w.isTable = true
return w
}
func showTable(t *guipb.Table) {
log.Info("gocui: should show table here")
if t == nil {
return
}
log.Info("gocui: table.Title", t.Title)
// log.Info("gocui: need to add window here id =", t.Window.Id, t.Window.Name)
if t.Grid == nil {
log.Info("gocui: missing grid widget. tree plugin error")
return
}
root := me.treeRoot.TK.(*guiWidget)
parent := root.findWidgetById(int(t.Parent.Id))
if parent == nil {
log.Info("gocui: show table error. parent.Id not found", t.Parent.Id)
return
}
log.Info("gocui: need to add grid here id =", t.Grid.Id)
grid := initGridPB(t.Grid)
grid.parent = parent
}
func enableWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
tk.Enable()
}
func disableWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
tk.Disable()
}
func showWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
tk.Show()
}
func hideWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
if n.WidgetType == widget.Window {
tk.windowFrame.Hide()
tk.hideWidgets()
}
tk.Hide()
tk.deleteWidget()
}
func (tk *guiWidget) deleteWidget() {
log.Log(INFO, "gocui deleteWidget() looking for child to delete:", tk.cuiName)
p := tk.parent
for i, child := range p.children {
if tk == child {
log.Log(INFO, "deleteWidget() found parent with child to delete:", i, child.cuiName, child.WidgetId())
p.children = slices.Delete(p.children, i, i+1)
}
}
tk.deleteTree()
}
func (tk *guiWidget) deleteTree() {
for _, child := range tk.children {
child.deleteTree()
}
tk.Hide()
}

View File

@ -1,151 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// simulates a dropdown menu in gocui
import (
"strings"
"time"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
)
func (tk *guiWidget) forceSizes(r *rectType) {
tk.gocuiSize.w0 = r.w0
tk.gocuiSize.w1 = r.w1
tk.gocuiSize.h0 = r.h0
tk.gocuiSize.h1 = r.h1
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
tk.force.w0 = r.w0
tk.force.w1 = r.w1
tk.force.h0 = r.h0
tk.force.h1 = r.h1
}
func initTextbox() {
if me.textbox.tk == nil {
// should only happen once
me.textbox.tk = makeNewFlagWidget(me.textbox.wId)
// me.textbox.tk.dumpWidget("init() textbox")
}
}
func (callertk *guiWidget) prepTextbox() {
initTextbox()
if me.textbox.tk == nil {
log.Log(WARN, "prepTextbox() Is Broken")
return
}
r := new(rectType)
// startW, startH := tk.Position()
r.w0 = callertk.gocuiSize.w0 + 4
r.h0 = callertk.gocuiSize.h0 + 3
r.w1 = r.w0 + 24
r.h1 = r.h0 + 2
me.textbox.tk.forceSizes(r)
me.textbox.tk.dumpWidget("after sizes")
me.textbox.callerTK = callertk
if me.textbox.tk.v != nil {
log.Log(WARN, "WARNING textbox DeleteView()")
log.Log(WARN, "WARNING textbox DeleteView()")
log.Log(WARN, "WARNING textbox DeleteView()")
me.baseGui.DeleteView(me.textbox.tk.cuiName)
time.Sleep(time.Second)
}
if err := me.textbox.tk.SetViewRect(r); err != nil {
log.Log(WARN, "textbox SetViewRect() failed", err, "view name =", me.textbox.tk.cuiName)
return
}
// me.textbox.tk.Show() // actually makes the gocui view. TODO: redo this?
showTextbox(callertk.String())
}
func showTextbox(callers string) {
// tk := me.textbox.tk
// me.textbox.tk.dumpWidget("after sizes")
log.Log(WARN, "showTextbox() caller string =", callers)
// me.textbox.tk.Show() // actually makes the gocui view. TODO: redo this
if me.textbox.tk.v == nil {
log.Log(WARN, "textbox.tk.v == nil showTextbox() is broken")
return
}
me.textbox.tk.setColorModal()
me.textbox.tk.v.Clear()
cur := strings.TrimSpace(callers)
// log.Info("setting textbox string to:", cur)
me.textbox.tk.v.WriteString(cur)
me.textbox.tk.v.Editable = true
me.textbox.tk.v.Wrap = true
me.textbox.tk.SetView()
me.baseGui.SetCurrentView(me.textbox.tk.v.Name())
// bind the enter key to a function so we can close the textbox
me.baseGui.SetKeybinding(me.textbox.tk.v.Name(), gocui.KeyEnter, gocui.ModNone, theCloseTheTextbox)
me.textbox.active = true
me.baseGui.SetViewOnTop(me.textbox.tk.v.Name())
me.textbox.tk.dumpWidget("showTextbox()")
}
func theCloseTheTextbox(g *gocui.Gui, v *gocui.View) error {
textboxClosed()
return nil
}
// updates the text and sends an event back to the application
func textboxClosed() {
// get the text the user entered
var newtext string
if me.textbox.tk.v == nil {
newtext = ""
} else {
newtext = me.textbox.tk.v.ViewBuffer()
}
newtext = strings.TrimSpace(newtext)
me.textbox.active = false
me.textbox.tk.Hide()
// log.Info("textbox closed with text:", newtext, me.textbox.callerTK.cuiName)
if me.notify.clock.tk.v != nil {
me.baseGui.SetCurrentView("help")
} else {
me.baseGui.SetCurrentView("msg")
}
// change the text of the caller widget
me.textbox.callerTK.SetText(newtext)
me.textbox.callerTK.node.SetCurrentS(newtext)
// send an event from the plugin with the new string
me.myTree.SendUserEvent(me.textbox.callerTK.node)
win := me.textbox.callerTK.findParentWindow()
if win != nil {
// win.dumpWidget("redraw this!!!")
tk := me.textbox.callerTK
// me.textbox.callerTK.dumpWidget("resize this!!!")
me.textbox.callerTK.Size()
me.textbox.callerTK.placeWidgets(tk.gocuiSize.w0-4, tk.gocuiSize.h0-4)
// tk.dumpWidget("resize:" + tk.String())
win.makeWindowActive()
}
}

View File

@ -1,90 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
/*
DO NOT EDIT THIS FILE
this file is the same for every GUI toolkit plugin
when you are making a new GUI toolkit plugin for
a specific toolkit, you just need to define these
functions.
for example, in the "gocui" toolkit, the functions
below are what triggers the "gocui" GO package
to draw labels, buttons, windows, etc
If you are starting out trying to make a new GUI toolkit,
all you have to do is copy this file over. Then
work on making these functions. addWidget(), setText(), etc.
That's it!
*/
package main
/*
This is reference code for toolkit developers
This is how information is passed in GO back to the application
via the GO 'plugin' concept
TODO: switch this to protocol buffers
*/
import (
"time"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
// Other goroutines must use this to access the GUI
//
// You can not acess / process the GUI thread directly from
// other goroutines. This is due to the nature of how
// Linux, MacOS and Windows work (they all work differently. suprise. surprise.)
//
// this sets the channel to send user events back from the plugin
func Callback(guiCallback chan widget.Action) {
me.myTree.Callback(guiCallback)
}
func PluginChannel() chan widget.Action {
initOnce.Do(initPlugin)
for {
if me.myTree != nil {
break
}
log.Info("me.myTree == nil")
time.Sleep(300 * time.Millisecond)
}
return me.myTree.PluginChannel()
}
func FrozenChannel() chan widget.Action {
return me.myTree.FrozenChannel()
}
func initTree() *tree.TreeInfo {
t := tree.New()
t.PluginName = PLUGIN
t.Add = newAdd
t.SetTitle = setTitle
t.SetLabel = setLabel
t.SetText = setText
t.AddText = addText
t.Enable = enableWidget
t.Disable = disableWidget
t.Show = showWidget
t.Hide = hideWidget
t.SetChecked = setChecked
t.ToolkitInit = toolkitInit
t.ToolkitClose = toolkitClose
t.ShowTable = showTable
return t
}

253
view.go
View File

@ -1,106 +1,233 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"bufio"
"errors"
"fmt"
"strings"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
// expands the gocuiSize rectangle to fit
// all the text in tk.labelN
func (tk *guiWidget) textResize() {
var w, h int = 0, 0
for _, s := range strings.Split(tk.labelN, "\n") {
s = strings.TrimSpace(s)
// log.Log(INFO, "textResize() len =", len(s), i, s)
if w < len(s) {
w = len(s)
}
h += 1
func splitLines(s string) []string {
var lines []string
sc := bufio.NewScanner(strings.NewReader(s))
for sc.Scan() {
lines = append(lines, sc.Text())
}
// todo: fix all this old code
if tk.WidgetType() == widget.Textbox {
if w < 5 {
w = 5
}
}
// this is old code. now move this somewhere smarter
tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + me.FramePadW // TODO: move this FramePadW out of here
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + me.FramePadH // TODO: fix this size computation
return lines
}
// deletes every view
func (w *guiWidget) hideWindow() {
if w == nil {
return
func (n *node) textResize() bool {
w := n.tk
var width, height int = 0, 0
var changed bool = false
for i, s := range splitLines(n.tk.label) {
log.Log(INFO, "textResize() len =", len(s), i, s)
if width < len(s) {
width = len(s)
}
height += 1
}
w.Hide()
for _, child := range w.children {
child.hideWindow()
if w.gocuiSize.w1 != w.gocuiSize.w0+width+me.FramePadW {
w.gocuiSize.w1 = w.gocuiSize.w0 + width + me.FramePadW
changed = true
}
if w.gocuiSize.h1 != w.gocuiSize.h0+height+me.FramePadH {
w.gocuiSize.h1 = w.gocuiSize.h0 + height + me.FramePadH
changed = true
}
if changed {
n.showWidgetPlacement(true, "textResize() changed")
}
return changed
}
func (w *guiWidget) hideWidgets() {
if w == nil {
func (n *node) hideView() {
n.SetVisible(false)
}
// display's the text of the widget in gocui
// will create a new gocui view if there isn't one or if it has been moved
func (n *node) showView() {
var err error
w := n.tk
if w.cuiName == "" {
log.Log(ERROR, "showView() w.cuiName was not set for widget", w)
w.cuiName = string(n.WidgetId)
}
// if the gocui element doesn't exist, create it
if w.v == nil {
n.recreateView()
}
x0, y0, x1, y1, err := me.baseGui.ViewPosition(w.cuiName)
log.Log(INFO, "showView() w.v already defined for widget", n.progname, err)
// n.smartGocuiSize()
changed := n.textResize()
if changed {
log.Log(NOW, "showView() textResize() changed. Should recreateView here wId =", w.cuiName)
} else {
log.Log(NOW, "showView() Clear() and Fprint() here wId =", w.cuiName)
w.v.Clear()
fmt.Fprint(w.v, n.tk.label)
n.SetVisible(false)
n.SetVisible(true)
return
}
switch w.WidgetType() {
// if the gocui element has changed where it is supposed to be on the screen
// recreate it
if x0 != w.gocuiSize.w0 {
n.recreateView()
return
}
if y0 != w.gocuiSize.h0 {
log.Log(ERROR, "showView() start hight mismatch id=", w.cuiName, "gocui h vs computed h =", w.gocuiSize.h0, y0)
n.recreateView()
return
}
if x1 != w.gocuiSize.w1 {
log.Log(ERROR, "showView() too wide", w.cuiName, "w,w", w.gocuiSize.w1, x1)
n.recreateView()
return
}
if y1 != w.gocuiSize.h1 {
log.Log(ERROR, "showView() too high", w.cuiName, "h,h", w.gocuiSize.h1, y1)
n.recreateView()
return
}
n.SetVisible(true)
}
// create or recreate the gocui widget visible
// deletes the old view if it exists and recreates it
func (n *node) recreateView() {
var err error
w := n.tk
log.Log(ERROR, "recreateView() START", n.WidgetType, n.progname)
if me.baseGui == nil {
log.Log(ERROR, "recreateView() ERROR: me.baseGui == nil", w)
return
}
// this deletes the button from gocui
me.baseGui.DeleteView(w.cuiName)
w.v = nil
if n.progname == "CLOUDFLARE_EMAIL" {
n.showWidgetPlacement(true, "n.progname="+n.progname+" n.tk.label="+n.tk.label+" "+w.cuiName)
n.dumpWidget("jwc")
n.textResize()
n.showWidgetPlacement(true, "n.progname="+n.progname+" n.tk.label="+n.tk.label+" "+w.cuiName)
}
a := w.gocuiSize.w0
b := w.gocuiSize.h0
c := w.gocuiSize.w1
d := w.gocuiSize.h1
w.v, err = me.baseGui.SetView(w.cuiName, a, b, c, d, 0)
if err == nil {
n.showWidgetPlacement(true, "recreateView()")
log.Log(ERROR, "recreateView() internal plugin error err = nil")
return
}
if !errors.Is(err, gocui.ErrUnknownView) {
n.showWidgetPlacement(true, "recreateView()")
log.Log(ERROR, "recreateView() internal plugin error error.IS()", err)
return
}
// this sets up the keybinding for the name of the window
// does this really need to be done? I think we probably already
// know everything about where all the widgets are so we could bypass
// the gocui package and just handle all the mouse events internally here (?)
// for now, the w.v.Name is a string "1", "2", "3", etc from the widgetId
// set the binding for this gocui view now that it has been created
// gocui handles overlaps of views so it will run on the view that is clicked on
me.baseGui.SetKeybinding(w.v.Name(), gocui.MouseLeft, gocui.ModNone, click)
// this actually sends the text to display to gocui
w.v.Wrap = true
w.v.Frame = w.frame
w.v.Clear()
fmt.Fprint(w.v, n.tk.label)
// n.showWidgetPlacement(true, "n.progname=" + n.progname + " n.tk.label=" + n.tk.label + " " + w.cuiName)
// n.dumpWidget("jwc 2")
// if you don't do this here, it will be black & white only
if w.color != nil {
w.v.FrameColor = w.color.frame
w.v.FgColor = w.color.fg
w.v.BgColor = w.color.bg
w.v.SelFgColor = w.color.selFg
w.v.SelBgColor = w.color.selBg
}
if n.progname == "CLOUDFLARE_EMAIL" {
n.showWidgetPlacement(true, "n.progname="+n.progname+" n.tk.label="+n.tk.label+" "+w.cuiName)
n.dumpTree(true)
}
log.Log(ERROR, "recreateView() END")
}
func (n *node) hideWidgets() {
w := n.tk
w.isCurrent = false
switch n.WidgetType {
case widget.Root:
case widget.Flag:
case widget.Window:
case widget.Box:
case widget.Grid:
default:
w.Hide()
n.hideView()
}
for _, child := range w.children {
for _, child := range n.children {
child.hideWidgets()
}
}
func hideFake() {
var w *guiWidget
w = me.treeRoot.TK.(*guiWidget)
w.hideFake()
}
func showFake() {
var w *guiWidget
w = me.treeRoot.TK.(*guiWidget)
w.showFake()
}
func (w *guiWidget) hideFake() {
func (n *node) hideFake() {
w := n.tk
if w.isFake {
w.Hide()
n.hideView()
}
for _, child := range w.children {
for _, child := range n.children {
child.hideFake()
}
}
// shows the 'fake' widgets for widgets that
// are not normally displayed (like a grid widget)
func (w *guiWidget) showFake() {
func (n *node) showFake() {
w := n.tk
if w.isFake {
w.drawView()
w.dumpWidget("in showFake()")
n.setFake()
n.showWidgetPlacement(true, "showFake:")
n.showView()
}
for _, child := range w.children {
for _, child := range n.children {
child.showFake()
}
}
func (w *guiWidget) showWidgets() {
w.Show()
for _, child := range w.children {
func (n *node) showWidgets() {
w := n.tk
if w.isFake {
// don't display by default
} else {
n.showWidgetPlacement(true, "current:")
n.showView()
}
for _, child := range n.children {
child.showWidgets()
}
}

139
widget.go Normal file
View File

@ -0,0 +1,139 @@
package main
import (
"go.wit.com/log"
"go.wit.com/widget"
)
func initWidget(n *node) *guiWidget {
var w *guiWidget
w = new(guiWidget)
// Set(w, "default")
w.frame = true
// set the name used by gocui to the id
w.cuiName = string(n.WidgetId)
if n.WidgetType == widget.Root {
log.Log(INFO, "setupWidget() FOUND ROOT w.id =", n.WidgetId)
n.WidgetId = 0
me.rootNode = n
return w
}
if n.WidgetType == widget.Grid {
w.widths = make(map[int]int) // how tall each row in the grid is
w.heights = make(map[int]int) // how wide each column in the grid is
}
return w
}
func setupCtrlDownWidget() {
a := new(widget.Action)
a.ProgName = "ctrlDown"
a.WidgetType = widget.Dialog
a.WidgetId = -1
a.ParentId = 0
n := addNode(a)
me.ctrlDown = n
}
func (n *node) deleteView() {
w := n.tk
if w.v != nil {
w.v.Visible = false
return
}
// make sure the view isn't really there
me.baseGui.DeleteView(w.cuiName)
w.v = nil
}
// searches the binary tree for a WidgetId
func (n *node) findWidgetName(name string) *node {
if n == nil {
return nil
}
if n.tk.cuiName == name {
return n
}
for _, child := range n.children {
newN := child.findWidgetName(name)
if newN != nil {
return newN
}
}
return nil
}
func (n *node) IsCurrent() bool {
w := n.tk
if n.WidgetType == widget.Tab {
return w.isCurrent
}
if n.WidgetType == widget.Window {
return w.isCurrent
}
if n.WidgetType == widget.Root {
return false
}
return n.parent.IsCurrent()
}
func (n *node) Visible() bool {
if n == nil {
return false
}
if n.tk == nil {
return false
}
if n.tk.v == nil {
return false
}
return n.tk.v.Visible
}
func (n *node) SetVisible(b bool) {
if n == nil {
return
}
if n.tk == nil {
return
}
if n.tk.v == nil {
return
}
n.tk.v.Visible = b
}
func addDropdown() *node {
n := new(node)
n.WidgetType = widget.Flag
n.WidgetId = -2
n.ParentId = 0
// copy the data from the action message
n.progname = "DropBox"
n.tk.label = "DropBox text"
// store the internal toolkit information
n.tk = new(guiWidget)
n.tk.frame = true
// set the name used by gocui to the id
n.tk.cuiName = "-1 dropbox"
n.tk.color = &colorFlag
// add this new widget on the binary tree
n.parent = me.rootNode
if n.parent != nil {
n.parent.children = append(n.parent.children, n)
}
return n
}

View File

@ -1,119 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
var fakeStartWidth int = me.FakeW
var fakeStartHeight int = me.TabH + me.FramePadH
// setup fake labels for non-visible things off screen
func setFake(n *tree.Node) {
var w *guiWidget
w = n.TK.(*guiWidget)
w.isFake = true
w.gocuiSetWH(fakeStartWidth, fakeStartHeight)
fakeStartHeight += w.gocuiSize.Height()
// TODO: use the actual max hight of the terminal window
if fakeStartHeight > 24 {
fakeStartHeight = me.TabH
fakeStartWidth += me.FakeW
}
}
// mostly just sets the colors of things
func addWidget(n *tree.Node) {
if !me.ok {
log.Log(INFO, "addWidget() START NOT OKAY")
log.Log(INFO, "addWidget() START NOT OKAY")
log.Log(INFO, "addWidget() START NOT OKAY")
waitOK()
}
tk := n.TK.(*guiWidget)
log.Log(INFO, "setStartWH() w.id =", n.WidgetId, "n.name", n.String())
switch n.WidgetType {
case widget.Root:
log.Log(INFO, "setStartWH() rootNode w.id =", n.WidgetId, "w.name", n.String())
setFake(n)
return
case widget.Flag:
setFake(n)
return
case widget.Window:
tk.frame = false
tk.labelN = tk.GetText() + " X"
me.newWindowTrigger <- tk
redoWindows(0, 0)
return
case widget.Stdout:
tk.labelN = "moreSTDOUT"
n.State.ProgName = "moreSTDOUT"
n.State.Label = "moreSTDOUT"
tk.isFake = true
return
case widget.Tab:
return
case widget.Button:
tk.setColorButton()
if tk.IsEnabled() {
} else {
tk.setColorDisable()
}
return
case widget.Checkbox:
tk.setColorInput()
tk.labelN = "X " + n.State.Label
return
case widget.Dropdown:
tk.setColorInput()
return
case widget.Textbox:
n.State.Label = ""
tk.labelN = " "
tk.setColorInput()
return
case widget.Combobox:
tk.setColorInput()
return
case widget.Box:
tk.isFake = true
setFake(n)
return
case widget.Grid:
tk.isFake = true
setFake(n)
return
case widget.Group:
tk.setColorLabel()
tk.frame = false
return
case widget.Label:
if tk.node.InTable() {
if tk.node.State.AtH == 0 {
// this is the table header
tk.setColorLabelTable()
} else {
// todo: highlight the whole table row
tk.setColorLabel()
}
} else {
tk.setColorLabel()
}
tk.frame = false
return
default:
/*
if n.IsCurrent() {
n.updateCurrent()
}
*/
}
tk.dumpWidget("addWidget()unknown")
}

View File

@ -1,126 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"strconv"
"strings"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func initWidget(n *tree.Node) *guiWidget {
var w *guiWidget
w = new(guiWidget)
w.node = n
w.cuiName = strconv.Itoa(w.WidgetId()) + " TK"
// w.WidgetType() = n.WidgetType
w.labelN = n.State.Label
if w.labelN == "" {
// remove this debugging hack once things are stable and fixed
w.labelN = n.GetProgName()
}
w.frame = true
w.enable = n.State.Enable
if n.WidgetType == widget.Root {
log.Log(INFO, "setupWidget() FOUND ROOT w.id =", n.WidgetId)
}
if n.WidgetType == widget.Grid {
w.widths = make(map[int]int) // how tall each row in the grid is
w.heights = make(map[int]int) // how wide each column in the grid is
}
p := n.Parent
if p == nil {
log.Log(ERROR, "parent == nil", w.String(), n.WidgetId, w.WidgetType())
return w
}
if p.TK == nil {
if n.WidgetId == 0 {
// this is a normal init condition
} else {
log.Log(ERROR, "parent.TK == nil", w.String(), n.WidgetId, w.WidgetType())
}
return w
}
// set the parent and append to parent children
var ptk *guiWidget
ptk = p.TK.(*guiWidget)
w.parent = ptk
ptk.children = append(ptk.children, w)
return w
}
func setupCtrlDownWidget() {
a := new(widget.Action)
a.ProgName = "ctrlDown"
a.WidgetType = widget.Dialog
a.WidgetId = -1
a.ParentId = 0
// n := addNode(a)
n := me.myTree.AddNode(a)
me.ctrlDown = n
}
func (w *guiWidget) deleteView() {
// make sure the view isn't really there
// log.Log(GOCUI, "deleteView()", w.cuiName, w.WidgetType(), w.WidgetId())
me.baseGui.DeleteView(w.cuiName)
w.v = nil
}
func (tk *guiWidget) String() string {
// deprecate this?
curval := strings.TrimSpace(tk.labelN)
if curval != "" {
return curval
}
curval = strings.TrimSpace(tk.GetLabel())
if curval != "" {
return curval
}
curval = tk.GetText()
if curval != "" {
return curval
}
curval = tk.node.String()
if curval != "" {
return curval
}
curval = strings.TrimSpace(tk.node.ProgName())
if curval != "" {
return curval
}
return ""
}
func (tk *guiWidget) Visible() bool {
if tk == nil {
return false
}
if tk.v == nil {
return false
}
tk.v.Visible = true
return true
}
func (tk *guiWidget) Hide() {
tk.deleteView()
}
func (tk *guiWidget) SetVisible(b bool) {
if b {
tk.Show()
} else {
tk.Hide()
}
}

View File

@ -1,266 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"errors"
"fmt"
"strconv"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/widget"
)
// don't draw widgets that are too far down the window
func (tk *guiWidget) doNotDraw() bool {
var check bool
switch tk.WidgetType() {
case widget.Button:
check = true
case widget.Label:
check = true
default:
}
if !check {
return false
}
win := tk.findParentWindow()
if win == nil {
// don't draw anything if you can't find the parent window
return true
}
h := tk.gocuiSize.h0 - win.gocuiSize.h0
if h > 20 {
return true
}
return false
}
// page widgets in the window
func (tk *guiWidget) pageWidget() *rectType {
r := new(rectType)
var check bool
switch tk.WidgetType() {
case widget.Button:
check = true
case widget.Label:
check = true
default:
}
if !check {
return nil
}
win := tk.findParentWindow()
if win == nil {
// don't draw anything if you can't find the parent window
return nil
}
r.w0 = tk.gocuiSize.w0
r.h0 = tk.gocuiSize.h0
r.w1 = tk.gocuiSize.w1
r.h1 = tk.gocuiSize.h1
// r.h0 = tk.gocuiSize.h0 - win.gocuiSize.h0
if r.h0 > 20 {
return r
}
return r
}
// display's the text of the widget in gocui
// deletes the old view if it exists and recreates it
func (tk *guiWidget) drawView() {
var err error
log.Log(INFO, "drawView() START", tk.WidgetType(), tk.String())
if me.baseGui == nil {
log.Log(ERROR, "drawView() ERROR: me.baseGui == nil", tk)
return
}
if tk.cuiName == "" {
log.Log(ERROR, "drawView() tk.cuiName was not set for widget", tk)
tk.cuiName = strconv.Itoa(tk.WidgetId()) + " TK"
}
log.Log(INFO, "drawView() labelN =", tk.labelN)
// this deletes the button from gocui
me.baseGui.DeleteView(tk.cuiName)
tk.v = nil
a := tk.gocuiSize.w0
b := tk.gocuiSize.h0
c := tk.gocuiSize.w1
d := tk.gocuiSize.h1
/*
// testing code for paging large windows
if tk.doNotDraw() {
return
}
if tk.window.pager != 0 {
if r := tk.pageWidget(); r == nil {
// if nil, draw whatever it is anyway
} else {
if r.Width() == 0 && r.Height() == 0 {
// don't draw empty stuff
return
}
a = r.w0
b = r.h0
c = r.w1
d = r.h1
}
}
if tk.WidgetType() == widget.Window || tk.WidgetType() == widget.Flag {
if tk.window.pager != 0 {
if tk.gocuiSize.Height() > 40 {
tk.window.large = true
tk.gocuiSize.h1 = tk.gocuiSize.h0 + 40
d = tk.gocuiSize.h1
}
}
}
*/
// this is all terrible. This sets the title. kinda
if tk.WidgetType() == widget.Window {
tk.textResize()
tk.full.w0 = tk.force.w0
tk.full.h0 = tk.force.h0
// for windows, make it the full size
a = tk.full.w0
b = tk.full.h0
c = tk.full.w0 + tk.gocuiSize.Width()
d = tk.full.h0 + tk.gocuiSize.Height()
} else {
if tk.internal {
// do nothing
} else {
tk.textResize() // resize gocui frame to the widget text
}
a = tk.gocuiSize.w0
b = tk.gocuiSize.h0
c = tk.gocuiSize.w1
d = tk.gocuiSize.h1
}
tk.v, err = me.baseGui.SetView(tk.cuiName, a, b, c, d, 0)
if err == nil {
tk.dumpWidget("drawView() err")
log.Log(ERROR, "drawView() internal plugin error err = nil")
return
}
if !errors.Is(err, gocui.ErrUnknownView) {
tk.dumpWidget("drawView() err")
log.Log(ERROR, "drawView() internal plugin error error.IS()", err)
return
}
if tk.v == nil {
log.Info("MUTEX FAIL. tk.v == nil here in drawView()")
log.Info("MUTEX FAIL. tk.v == nil here in drawView()")
log.Info("MUTEX FAIL. tk.v == nil here in drawView()")
return
}
// this actually sends the text to display to gocui
tk.v.Wrap = true
tk.v.Frame = tk.frame
tk.v.Clear()
fmt.Fprint(tk.v, tk.labelN)
// tmp hack to disable buttons on window open
if tk.WidgetType() == widget.Button {
if tk.IsEnabled() {
} else {
tk.setColorDisable()
}
}
switch tk.WidgetType() {
case widget.Button:
if tk.IsEnabled() {
if tk.isDense() && tk.isInGrid() {
tk.setColorButtonDense()
} else {
tk.setColorButton()
}
} else {
tk.setColorDisable()
}
default:
}
if tk.v == nil {
log.Info("MUTEX FAIL 2. tk.v was deleted somehow tk.v == nil here in drawView()")
log.Info("MUTEX FAIL 2. tk.v == nil here in drawView()")
log.Info("MUTEX FAIL 2. tk.v == nil here in drawView()")
return
}
// if you don't do this here, it will be black & white only
if tk.color != nil {
tk.v.FrameColor = tk.color.frame
tk.v.FgColor = tk.color.fg
tk.v.BgColor = tk.color.bg
tk.v.SelFgColor = tk.color.selFg
tk.v.SelBgColor = tk.color.selBg
}
log.Log(INFO, "drawView() END")
}
// redraw the widget tree starting at this location
func (w *guiWidget) DrawAt(offsetW, offsetH int) {
w.placeWidgets(offsetW, offsetH) // compute the sizes & places for each widget
// w.dumpWidget(fmt.Sprintf("DrawAt(%d,%d)", offsetW, offsetH))
}
// display the widgets in the binary tree
func (w *guiWidget) drawTree(draw bool) {
if w == nil {
return
}
w.dumpWidget("in drawTree()")
if draw {
// w.textResize()
w.Show()
} else {
w.Hide()
}
for _, child := range w.children {
child.drawTree(draw)
}
}
func (w *guiWidget) Show() {
if w.isFake {
// don't display fake widgets
return
}
if w.Hidden() {
// recursively checks if the parent is hidden
// never show hidden widgets
return
}
if me.debug {
w.dumpWidget("drawView()")
}
w.drawView()
}

240
window.go
View File

@ -1,240 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"strings"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func (tk *guiWidget) setTitle(s string) {
if tk.WidgetType() != widget.Window {
return
}
if tk.v == nil {
return
}
tk.setColorWindowTitleActive()
rect := tk.gocuiSize
rect.w1 = rect.w0 + tk.full.Width() + 1
// rect.h1 = rect.h0 + 1
me.baseGui.SetView(tk.v.Name(), rect.w0-1, rect.h0, rect.w1+1, rect.h1, 0)
tk.v.Clear()
f := " %-" + fmt.Sprintf("%d", tk.full.Width()-3) + "s %s"
tmp := tk.GetLabel()
labelN := fmt.Sprintf(f, tmp, "X")
tk.v.WriteString(labelN)
}
func (tk *guiWidget) redrawWindow(w int, h int) {
if tk.WidgetType() != widget.Window {
return
}
// tk.dumpWidget(fmt.Sprintf("redrawWindow(%d,%d)", w, h))
if tk.full.Height() > 40 {
tk.window.dense = true
}
// pin the window to (w,h)
tk.gocuiSize.w0 = w
tk.gocuiSize.h0 = h
tk.gocuiSize.w1 = w + len(tk.GetLabel())
tk.labelN = tk.GetLabel() // could set XX here also but don't have final size of window yet
tk.force.w0 = w
tk.force.w1 = w
tk.force.h0 = h
tk.force.h1 = h
tk.setFullSize() // might make the green box the right size
tk.frame = false
tk.hasTabs = false
tk.DrawAt(w, h)
// tk.setColor(&colorActiveW) // sets the window to Green BG
tk.setColorWindowTitleActive()
if tk.window.collapsed {
// don't show anything but the title bar
tk.hideWindow()
return
}
tk.placeWidgets(w, h) // compute the sizes & places for each widget
// this is a test. this should not be needed
tk.full.w0 = tk.force.w0
tk.full.h0 = tk.force.h0
tk.setFullSize()
tk.Show()
if tk.v == nil {
log.Info("redrawWindow on tk.v == nil")
standardExit()
}
tk.v.Clear()
fmt.Fprint(tk.v, "ZZZ"+tk.GetText())
tk.showWidgets()
if tk.windowFrame == nil {
tk.addWindowFrameTK(0 - tk.WidgetId())
tk.windowFrame.makeTK([]string{""})
}
// this seems to correctly create the window frame
r := tk.getFullSize()
tk.windowFrame.gocuiSize.w0 = tk.force.w0
tk.windowFrame.gocuiSize.w1 = r.w1 + 1
tk.windowFrame.gocuiSize.h0 = tk.force.h0 + 2
tk.windowFrame.gocuiSize.h1 = r.h1 + 1
tk.windowFrame.full.w0 = tk.force.w0
tk.windowFrame.full.w1 = r.w1 + 1
tk.windowFrame.full.h0 = tk.force.h0 + 2
tk.windowFrame.full.h1 = r.h1 + 1
tk.windowFrame.setColorWindowFrame()
tk.windowFrame.Hide()
tk.windowFrame.Show()
// set the window frame below the window widget, but this resizes the window widget it seems
me.baseGui.SetViewBeneath(tk.windowFrame.cuiName, tk.cuiName, 1)
// so now we have to resize the window frame, but this moves it to the top?
me.baseGui.SetView(tk.windowFrame.cuiName, tk.windowFrame.full.w0, tk.windowFrame.full.h0, tk.windowFrame.full.w1, tk.windowFrame.full.h1, 0)
// so we have to redraw the widgets in the window anyway and then they will appear above he frame
tk.hideWidgets()
tk.showWidgets()
// draw the window title
tk.setTitle(tk.GetLabel())
}
// re-draws the buttons for each of the windows
func redoWindows(nextW int, nextH int) {
for _, tk := range findWindows() {
// tk.dumpWidget(fmt.Sprintf("redoWindowsS (%d,%d)", nextW, nextH))
if tk.window.wasDragged {
// don't move windows around the user has dragged to a certain location
tk.makeWindowActive()
} else {
w, _ := me.baseGui.Size()
if nextW > w-20 {
nextW = 0
nextH += 4
}
// probably a new window?
tk.redrawWindow(nextW, nextH)
}
// tk.dumpWidget(fmt.Sprintf("redoWindowsE (%d,%d)", nextW, nextH))
// increment the width for the next window
nextW += tk.gocuiSize.Width() + 10
// nextH += 10
}
}
func (tk *guiWidget) addWindowFrameTK(wId int) {
n := tk.addWindowFrame(wId)
tk.windowFrame = n.TK.(*guiWidget)
}
func (win *guiWidget) addWindowFrame(wId int) *tree.Node {
n := new(tree.Node)
n.WidgetType = widget.Flag
n.WidgetId = wId
n.ParentId = 0
// store the internal toolkit information
tk := new(guiWidget)
tk.frame = true
tk.labelN = "windowFrame text"
tk.internal = true
tk.node = n
if tk.node.Parent == nil {
tk.node.Parent = me.treeRoot
}
// set the name used by gocui to the id
tk.cuiName = fmt.Sprintf("%d DR", wId)
// tk.color = &colorGroup
// add this new widget on the binary tree
tk.parent = win
if tk.parent == nil {
panic("addDropdown() didn't get treeRoot guiWidget")
} else {
tk.parent.children = append(tk.parent.children, tk)
}
n.TK = tk
return n
}
func (tk *guiWidget) isWindowActive() bool {
if !(tk.WidgetType() == widget.Window || tk.WidgetType() == widget.Stdout) {
// only allow Window or the Stdout widgets to be made active
return false
}
return tk.window.active
}
// always redraws at the corner of the gocuiSize box
func (tk *guiWidget) makeWindowActive() {
if !(tk.WidgetType() == widget.Window || tk.WidgetType() == widget.Stdout) {
// only allow Window or the Stdout widgets to be made active
return
}
if tk.WidgetType() == widget.Stdout {
me.stdout.outputOnTop = true
} else {
// me.stdout.outputOnTop = false // ?
}
// disable and increment all the windows
for _, tk := range me.allwin {
tk.window.order += 1
tk.window.active = false
// tk.setColor(&colorWindow) // color for inactive windows
tk.setColorWindowTitle()
}
// set this window as the active one
tk.window.active = true
tk.window.order = 0
tk.redrawWindow(tk.gocuiSize.w0, tk.gocuiSize.h0)
setThingsOnTop() // sets help, Stdout, etc on the top after windows have been redrawn
}
func (tk *guiWidget) makeTK(ddItems []string) {
items := strings.Join(ddItems, "\n")
tk.labelN = items
tk.SetText(items)
tk.gocuiSize.w0 = 100
tk.gocuiSize.w1 = 120
tk.gocuiSize.h0 = 15
tk.gocuiSize.h1 = 18
tk.Show()
}
func (win *guiWidget) checkWindowClose(w int, h int) bool {
s := fmt.Sprintf("mouse(%d,%d) ", w, h)
offW := win.full.w1 - w
offH := h - win.full.h0
s += fmt.Sprintf("offset(%d,%d)", offW, offH)
if (offW < 2) && (offH < 2) {
log.Info("attempting close on ", s, win.cuiName)
me.myTree.SendWindowCloseEvent(win.node)
// store the stdout corner for computing the drag size
return true
}
// log.Info("not attempting close on ", s, win.cuiName)
return false
}