pax_global_header 0000666 0000000 0000000 00000000064 14704761225 0014522 g ustar 00root root 0000000 0000000 52 comment=0e30fc6cad39ebd429b8b30e43b85da1f01da7aa
durdraw-0.28.0/ 0000775 0000000 0000000 00000000000 14704761225 0013261 5 ustar 00root root 0000000 0000000 durdraw-0.28.0/.dockerignore 0000664 0000000 0000000 00000000005 14704761225 0015730 0 ustar 00root root 0000000 0000000 .git
durdraw-0.28.0/.github/ 0000775 0000000 0000000 00000000000 14704761225 0014621 5 ustar 00root root 0000000 0000000 durdraw-0.28.0/.github/FUNDING.yml 0000664 0000000 0000000 00000001455 14704761225 0016443 0 ustar 00root root 0000000 0000000 # These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: samfoster # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
durdraw-0.28.0/CONTRIBUTING.md 0000664 0000000 0000000 00000002530 14704761225 0015512 0 ustar 00root root 0000000 0000000 ## Contributing to Durdraw
That's awesome that you want to help! I thank you. The guidelines are pretty loose right now.
* The main thing is, work in the "dev" branch instead of the "master" branch. The changes made to the Dev branch are eventually merged into Master for release.
* If you implement a new feature that changes any behavior, unless it's really cool and makes a lot of sense, try to make it optional. For example, through a command-line option or runtime toggle.
* If you want to help "clean up" the code to make it more legible or modular, that's great. But, I request that you let me see some of the style changes you are proposing before you dump a giant patch of changes. I don't want major changes in a style I don't enjoy.
* Use Git and Github for development, discussion, pull requests, issues, proposals, etc. You can also contact the developer directly via email (see README.md CREDITS), or you can find us on Discord or IRC (see README.md for details).
* If you want to contribute ANSI or ASCII Art, that's great! Please post it in the Discussions on Github and give consent if you are OK with it being used (in the program, website, Readme file, youtube videos, etc).
* Reviews, feedback, tutorials, demonstrations, etc. are also welcome. Post any art you created with Durdraw in the Discussions, because we would love to see them!
durdraw-0.28.0/Dockerfile 0000664 0000000 0000000 00000000261 14704761225 0015252 0 ustar 00root root 0000000 0000000 # syntax=docker/dockerfile:1
FROM python:3.12-alpine
WORKDIR /durdraw
COPY . .
RUN pip install --upgrade .
#RUN ./installconf.sh
ENV TERM=xterm-256color
ENTRYPOINT ["durdraw"]
durdraw-0.28.0/LICENSE 0000664 0000000 0000000 00000002737 14704761225 0014277 0 ustar 00root root 0000000 0000000 BSD 3-Clause License
Copyright (c) 2009-2024, Sam Foster
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
durdraw-0.28.0/README.md 0000664 0000000 0000000 00000052747 14704761225 0014557 0 ustar 00root root 0000000 0000000 Durdraw
=======
__ __
_| |__ __ _____ __| |_____ _____ __ __ __
/ _ | | | __| _ | __| _ | | | |\
/_____|_____|__|__|_____|__|___\____|________| |
\_____________________________________________\| v 0.28.0

## OVERVIEW
Durdraw is an ASCII, Unicode and ANSI art editor for UNIX-like systems (Linux,
macOS, etc). It runs in modern Utf-8 terminals and supports frame-based
animation, custom themes, 256 and 16 color modes, terminal mouse input, DOS
ANSI art viewing, CP437 and Unicode mixing and conversion, HTML output, mIRC
color output, and other interesting features.
Durdraw is heavily inspired by classic ANSI editing software for MS-DOS and
Windows, such as TheDraw, Aciddraw and Pablodraw, but with a modern Unix twist.
## REQUIREMENTS
* Python 3 (3.10+ recommended)
* Linux, macOS, or other Unix-like System
## INSTALLATION
You can install from your OS repositories, or follow the instructions below to install from source:
[](https://repology.org/project/durdraw/versions)
If you just want to run it without instalilng, scroll down to the next section.
1: Download and extract, or use git to download:
```
git clone https://github.com/cmang/durdraw.git
cd durdraw
```
2: Install or upgrade using pip:
```
pip install --upgrade .
```
Or run the installer:
```
python3 setup.py install
```
3: Optionally, install some themes and a sample configuration file for your local user into ~/.durdraw/:
```
./installconf.sh
```
You should now be able to run `durdraw`. Press `esc-h` for help, or try `durdraw --help` for command-line options.
## RUNNING WITHOUT INSTALLING
You can run Durdraw with:
```
./start-durdraw
```
To look at some included example animations:
```
./start-durdraw -p examples/*.dur
```
## OPTIONAL INSTALLATION
For PNG and animated GIF export, please install Ansilove (https://ansilove.org/) and make sure it is is in your path. PNG and GIF export only work in 16-color mode for now, and only with CP437 compatible charcters. You also need the PIL python module.
For Durfetch support, please install Neofetch and place it in your path.
## GALLERY
[](https://youtu.be/vWczO0Vd_54)
[](https://youtu.be/7Icf08bkJxg)







## COMMAND LINE USAGE
You can play a .dur file or series of .dur (or .ANS or .ASC) files with:
```
$ durdraw -p filename.dur
$ durdraw -p file1.dur file2.dur file3.dur ...
```
Or view a downloaded ANSI artpack with:
```
$ durdraw -p *.DIZ *.ASC *.ANS
```
Other command-line options:
usage: durdraw [-h] [-p PLAY [PLAY ...]] [-d DELAYEXIT] [-x TIMES] [--256color | --16color] [-b] [-W WIDTH] [-H HEIGHT] [-m]
[--wrap WRAP] [--nomouse] [--cursor CURSOR] [--notheme] [--theme THEME] [--cp437] [--export-ansi] [-u UNDOSIZE]
[--fetch] [-V]
[filename]
positional arguments:
filename .dur or ascii file to load
options:
-h, --help show this help message and exit
-p PLAY [PLAY ...], --play PLAY [PLAY ...]
Just play .dur, .ANS or .ASC file or files, then exit
-d DELAYEXIT, --delayexit DELAYEXIT
Wait X seconds after playback before exiting (requires -p)
-x TIMES, --times TIMES
Play X number of times (requires -p)
--256color Try 256 color mode
--16color Try 16 color mode
-b, --blackbg Use a black background color instead of terminal default
-W WIDTH, --width WIDTH
Set canvas width
-H HEIGHT, --height HEIGHT
Set canvas height
-m, --max Maximum canvas size for terminal (overrides -W and -H)
--wrap WRAP Number of columns to wrap lines at when loading ASCII and ANSI files (default 80)
--nomouse Disable mouse support
--cursor CURSOR Cursor mode (block, underscore, or pipe)
--notheme Disable theme support (use default theme)
--theme THEME Load a custom theme file
--cp437 Display extended characters on the screen using Code Page 437 (IBM-PC/MS-DOS) encoding instead of Utf-8.
(Requires CP437 capable terminal and font) (beta)
--export-ansi Export loaded art to an .ansi file and exit
-u UNDOSIZE, --undosize UNDOSIZE
Set the number of undo history states - default is 100. More requires more RAM, less saves RAM.
--fetch Replace fetch strings with Neofetch output
-V, --version Show version number and exit
## INTERACTIVE USAGE/EDITING
Use the arrow keys (or mouse) and other keys to edit, much like a text editor.
You can click highlighted areas of the screen.
You can use the "Esc" (or "Meta") key to access keyboar shortcuts and commands:
```
____________. _________ __________ _________ _____ _______
.-\\___ / |______/ _ /.-\\___ // _ /_/ _ \_.____. \ /
| |/ / | / / /:| |/ / / /Y Y Y | / /
| / /| | / _ _/ || / /: _ _/ : _ | /\/ /
| /:| : : Y |: /:| Y | Y | /:H7
|____ |_________|___| |_____ |____| | |____|____/\_____|
.-- `-----' ----------- `------': - `-----' -- `------'----' -----------------.
| |
`-----------------------------------------------------------------------------'
.. Art Editing ..................... .. Animation .......................
: F1-F10 - insert character : : esc-k - next frame :
: esc-1 to esc-0 - same as F1-F10 : : esc-j - previous frame :
: esc-space - insert draw char : : esc-p - start/stop payback :
: esc-c/tab - color picker : : esc-n - clone frame :
: esc-left - next fg color : : esc-N - append empty frame :
: esc-right - prev fg color : : esc-d - delete frame :
: esc-up - change color up : : esc-D - set frame delay :
: esc-down - change color down : : esc-+/esc-- - faster/slower :
: esc-/ - insert line : : esc-R - set playback/edit range :
: esc-' - delete line : : esc-g - go to frame # :
: esc-. - insert column : : esc-M - move frame :
: esc-, - delete column : : esc-{ - shift frames left :
: esc-] - next character group : : esc-} - shift frames right :
: esc-[ - previous character group : :..................................:
: esc-S - change character set :
: esc-L - replace color : .. UI/Misc .........................
: esc-y - eyedrop (pick up color) : : esc-m - main menu :
: esc-P - pick up character : : esc-a - animation menu :
: esc-l - color character : : esc-t - mouse tools :
: shift-arrows - select for copy : : esc-z - undo :
: esc-K - mark selection : : esc-r - redo :
: esc-v - paste : : esc-V - view mode :
:..................................: : esc-i - file/canvas info :
: esc-I - character inspector :
.. File Operations ................. : esc-F - search/find string :
: esc-C - new/clear canvas : : ctrl-l - redraw screen :
: esc-o - open : : esc-h - help :
: esc-s - save : : esc-q - quit :
:..................................: :..................................:
.. Canvas Size .....................
: esc-" - insert line :
: esc-: - delete line :
: esc-> - insert column :
: esc-< - delete column :
:..................................:
esc-j esc-k
Prev Next Canvas
esc-f esc-g esc-- Frame Frame Size
esc-m Go to esc-+ esc-D esc-R esc-t | esc-p| |
Main Frame Speed Frame Play/Edit Mouse First | Play/| Last |
Menu Number | Delay Range Tools Frame | Pause| Frame |
| | | | | | | | | | | |
[Menu] F: 1/7 : 8 D: 0.00 R: 1/8 [Move] |< << |> >> >| [80x24]
tab
esc-c esc-S
Pick esc-[ esc-] Charset set F1-F10 esc-[ esc-]
Foreground Character or Unicode Insert Special Prev/Next Cursor
Color Group Block Characters Char Group Position
| | | | \ |
FG:██ (1/21) [Dur..] (12,10)
ANIMATION:
Use the Animation Menu [Anim] or keyboard commands to insert (esc-n),
delete (esc-d), move (esc-M) and edit frames. Use esc-k and esc-j to
flip to the next and previous frames. The "Play" button (|> or esc-p)
starts or stops playback.
When the animation is playing, all changes made effect all frames
within the current playback/edit Range (R: or esc-R). Change playback
speed ( or Frames Per Second) with esc-+ (or esc-=) and esc--.
F: shows the current frame number, and you can go to a specific frame
with esc-g.
BRUSHES:
To make a brush, use shift-arrow or esc-K to make a selection, then
press b. To use the brush, click the Mouse Tools menu (esc-t) and select
Paint (P). You can now use the mouse to paint with your custom brush.
```
## CONFIGURATION
You can create a custom startup file where you can set a theme and other options.
If you did not already do so during installation, you can install a sample configuration and some themes into ~/.durdraw/ with the command:
```
./installconf.sh
```
This will place durdraw.ini into ~/.durdraw/ and the themes into ~/.durdraw/themes/.
Here is an example durdraw.ini file, showing the available options:
; Durdraw 0.28.0 Configuration File
[Main]
; color-mode sets the color mode to start in. Available options: 16, 256
;color-mode: 16
; disable-mouse disablse the mouse.
;disable-mouse: True
; max-canvas atuomatically sets the canvas size to the terminal window size at startup.
;max-canvas: True
; Cursor mode requests a cursor type from the terminal. Available options: block, underscore, pipe
;cursor-mode: underscore
; When scroll-colors is enabled, using the mouse wheel in the canvas changes the
; foreground color instead of moving the cursor.
;scroll-colors: True
[Theme]
theme-16: ~/.durdraw/themes/mutedchill-16.dtheme.ini
theme-256: ~/.durdraw/themes/mutedform-256.dtheme.ini
The option 'theme-16' sets the path to the theme file used in 16-color mode, and 'theme-256' sets the theme file used for 256-color mode.
You can also load a custom theme file using the --theme command-line argument and passing it the path to a theme file, or disable themes entirely with the --notheme command line option.
Here is an example 16-color theme:
[Theme-16]
name: 'Purple Drank'
mainColor: 6
clickColor: 3
borderColor: 6
clickHighlightColor: 5
notificationColor: 4
promptColor: 4
and a 256-color theme:
[Theme-256]
name: 'Muted Form'
mainColor: 104
clickColor: 37
borderColor: 236
clickHighlightColor: 15
notificationColor: 87
promptColor: 189
menuItemColor: 189
menuTitleColor: 159
menuBorderColor: 24
The colors and theme options are as follows:
colors for 16-color mode:
1 black
2 blue
3 green
4 cyan
5 red
6 magenta
7 yellow
8 white
color codes numbers for 256-color mode can be found in Durdraw's 256-color selector.
```
mainColor: the color of most text
clickColor: the color of buttons (clickable items)
clickHighlightColor: the color the button changes to for a moment when clicked
borderColor: the color of the border around a drawing
notificationColor: the color of notification messages
promptColor: the color of user prompt messages
menuItemColor: the color of menu items
menuTitleColor: the color of menu titles
menuBorderColor: the color of the border around menus
```
## DURFETCH
Durfetch is a program which acts like a fetcher. It uses Neofetch to obtain system statistics and requires that Neofetch be found in the path. You can put keys in your .DUR files which durfetch will replace with values from Neofetch. You can also use built-in example animations.
Note that this feature is in beta, and is far from perfect, but it can be fun to play with. If anyone wants to improve durfetch, please feel free.
Keys will only be replaced if there is enough room in the art for the replacement value.
The following values can be used in your art and automatically interpreted by Durfetch:
```
{OS}
{Host}
{Kernel}
{Uptime}
{Packages}
{Shell}
{Resolution}
{DE}
{WM}
{WM Theme}
{Terminal}
{Terminal Font}
{CPU}
{GPU}
{Memory}
```
The durfetch executable takes the following command-line paramaters:
```
usage: durfetch [-h] [-r | -l LOAD] [--linux | --bsd] [filename ...]
An animated fetcher. A front-end for Durdraw and Neofetch integration.
positional arguments:
filename .durf ASCII and ANSI art file or files to use
options:
-h, --help show this help message and exit
-r, --rand Pick a random animation to play
-l LOAD, --load LOAD Load an internal animation
--linux Show a Linux animation
--bsd Show a BSD animation
Available animations for -l:
bsd
cm-eye
linux-fire
linux-tux
unixbox
```
Here are some durfetch examples:


## FAQ
#### Q: Durdraw crashed! What do I do?
A: Oh no! I am sorry and hope nothing important was lost. But you can help fix it. Please take a screenshot of the crash and post it as a bug report at https://github.com/cmang/durdraw/issues/. Please try to describe what you were trying to do when it happened, and if possible, include the name of your terminal, OS and Python version. I will do my best to try to fix it ASAP. Your terminal will probably start acting weird if Durdraw crashed. You can usually fix it by typing "reset" and pressing enter.
#### Q: Don't TheDraw and some other programs already do ANSI animation?
A: Yes, but traditional ANSI animation does not provide any control over timing, instead relying on terminal baud rate to govern the playback speed. This does not work well on modern systems without baud rate emulation. Durdraw gives the artist fine control over frame rate, and delays per frame. Traditional ANSI animation also updates the animation one character at a time, while Durdraw updates the animation a full frame at a time. This makes it less vulnerable to visual corruption from things like errant terminal characters, resized windows, line noise, etc. Finally, unlike TheDraw, which requires MS-DOS, Durdraw runs in modern Unicode terminals.
#### Q: Can I run Durdraw in Windows?
A: Short answer: It's not supported, but it seems to work fine in the Windows Subsystem for Linux (WSL), and in Docker using the provided Dockerfile. Long answer: Some versions run fine in Windows Command Prompt, Windows Terminal, etc, without WSL, but it's not tested or supported. If you want to help make Durdraw work better in Windows, please help by testing, submitting bug reports and submitting patches.
#### Q: Can I run Durdraw on Amiga, MS-DOS, Classic MacOS, iOS, Android, Atari ST, etc?
A: Probably not easily. Durdraw requires Python 3 and Ncurses. If your platform can support these, it will probably run. However, the file format for Durdraw movies is a plain text JSON format. It should be possible to support this format in different operating systems and in different applications. See durformat.md for more details on the .dur file format.
#### Q: Does Durdraw support IBM-PC ANSI art?
A: Yes! IBM-PC ANSI art popular in the "ANSI Art Scene" uses Code Page 437 character encoding, which usually needs to be translated to work with modern terminals. When Durdraw encounters these files, it will convert them to Unicode and carry on. When you save ANSI files, it will ask if you want to use CP437 or Utf-8 encoding.
#### Q: I only see 8 colors in 16 color mode. Why?
A: Look in your terminal setting for "Use bright colors for bold," or a similarly named option. Durdraw's 16-color mode, like many vintage terminals (including MS-DOS), uses the Bold escape codes to tell the terminal that colors are "bright." This provides compatibility with many older systems. However, some terminals do not support or enable this option by default. Additionally, your terminal decides what colors to assign to the lower 16 colors. In many terminals, Durdraw can override the default 16 color palette. To do this, click on Menu -> Settings and select VGA, Commodore 64 or ZX Spectrum colors.
#### Q: Some or all of the F1-F10 keys do not work for me! What can I do?
A: You can use ESC-1 through ESC-0 as a replacement for F1-F10. Some terminals will map this to Alt-1 through Alt-0. You can also use the following settings in some terminals to enable the F1-F10 keys:
- **GNOME Terminal**: **Click**: Menu -> Edit -> Preferences -> General, and **uncheck** the box:
- [ ] Enable the menu accelerator key (F10 by default)
- **Xfce4-Terminal**: **Click**: Menu -> Edit -> Preferences -> Advanced, and **check** the 2 boxes:
- [x] Disable menu shortcut key (F10 by default)
- [x] Disable help window shortcut key (F1 by default)
## LINKS, MEDIA AND THANKS
Special thanks to the following individuals and organizations for featuring Durdraw in their content:
Linux Magazine - https://www.linux-magazine.com/Issues/2024/281
Linux Voice Magazine - https://archive.org/details/LinuxVoice/Linux-Voice-Issue-015/page/n71/mode/2up
Bryan Lunduke at The Lunduke Journal - https://lunduke.locals.com/post/5327347/durdraw-like-thedraw-but-linux
Korben - https://korben.info/editeur-ansi-ascii-unicode-durdraw-creer-art-terminal.html
Jill Bryant and Venn Stone at Linux Game Cast - https://www.youtube.com/watch?v=HvZXkqg2vec&t=568s
LinuxLinks - https://www.linuxlinks.com/durdraw-ascii-unicode-ansi-art-editor/
Harald Markus Wirth (hmw) has made a Web .Dur Player in JavaScript: https://harald.ist.org/stubs/webdurplayer/
If you write, podcast, vlog, or create content about Durdraw, or if you simply enjoy using it, I'd love to hear from you! Please reach out to me via the GitHub project page or at samfoster@gmail.com.
## SUPPORT
Your support means a lot to Durdraw! As a free and open-source project, your donations fuel my motivation to keep improving this software. Thank you for considering a contribution to help sustain and enhance this project.
Contributions help cover essential costs like development time, domain registration, and web hosting.
You can contribute to this project using any of these platforms:
Paypal - https://www.paypal.com/donate/?hosted_button_id=VTPZPFMDLY4X6
Buymeacoffee - https://buymeacoffee.com/samfoster
Patreon - https://patreon.com/SamFoster
Other ways to support Durdraw include reporting bugs, providing feedback, and contributing code. Please refer to the CONTRIBUTING.md file for information and guidelines.
If you need assistance or have questions about Durdraw, feel free to reach out to us on GitHub. We're happy to help!
## COMMUNITY
There are community discussions on Github, where people post art made with Durdraw. Check it out: https://github.com/cmang/durdraw/discussions
We also have a Discord server for Durdraw users. Join us: https://discord.gg/9TrCsUrtZD
If you are feeling really old school, you can try the #durdraw IRC channel on irc.libera.chat.
## CREDITS
Developer: Sam Foster . For a full list of contributors, see the github page below.
Home page: http://durdraw.org
Development: https://github.com/cmang/durdraw
ANSI and ASCII artists: cmang, H7, LDA
## LEGAL
Durdraw is Copyright (c) 2009-2024 Sam Foster . All rights reserved.
The BSD Daemon is Copyright 1988 by Marshall Kirk McKusick.
This software is distributed under the BSD 3-Clause License. See LICENSE file for details.
durdraw-0.28.0/durdraw.1 0000664 0000000 0000000 00000005707 14704761225 0015024 0 ustar 00root root 0000000 0000000 .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.\" I edited it. Bad boys, bad boys. -Sam
.\" __ __
.\" _| |__ __ _____ __| |_____ _____ __ __ __
.\" / _ | | | __| _ | __| _ | | | |\
.\" /_____|_____|__|__|_____|__|___\____|________| |
.\" \_____________________________________________\|
.TH DURDRAW "1" "March 2024" "durdraw 0.26.0" "User Commands"
.SH NAME
durdraw \- versatile ASCII and ANSI Art text editor for drawing in terminal
.SH SYNOPSIS
.br
.B durdraw
[\-h] [\-p PLAY [PLAY ...]] [\-\-startup | \fB\-w\fR | \fB\-x\fR TIMES]
[\-\-256color | \fB\-\-16color]\fR [\-b] [\-W WIDTH] [\-H HEIGHT] [\-m]
[\-\-nomouse] [\-\-cursor CURSOR] [\-\-notheme] [\-\-theme THEME]
[\-\-cp437] [\-\-export\-ansi] [\-u UNDOSIZE] [\-V]
[filename]
.SH DESCRIPTION
Durdraw
is an ASCII, ANSI and Unicode art editor for UNIX-like systems (Linux,
macOS, etc). It runs in modern Utf-8 terminals and supports frame-based
animation, custom themes, 256 and 16 color modes, terminal mouse input,
DOS ANSI art viewing, CP437 and Unicode mixing and conversion, HTML output,
mIRC color output, and other interesting features.
.PP
Durdraw is heavily inspired by classic ANSI editing software for MS-DOS
and Windows, such as TheDraw, Aciddraw and Pablodraw, but with a modern Unix
twist.
.PP
.SS "positional arguments:"
.TP
filename
\&.dur or ascii file to load
.SS "options:"
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-p\fR PLAY [PLAY ...], \fB\-\-play\fR PLAY [PLAY ...]
Just play .dur file or files, then exit
.TP
\fB\-\-startup\fR
Show startup screen
.TP
\fB\-w\fR, \fB\-\-wait\fR
Pause at startup screen
.TP
\fB\-x\fR TIMES, \fB\-\-times\fR TIMES
Play X number of times (requires \fB\-p\fR)
.TP
\fB\-\-256color\fR
Try 256 color mode
.TP
\fB\-\-16color\fR
Try 16 color mode
.TP
\fB\-b\fR, \fB\-\-blackbg\fR
Use a black background color instead of terminal
default
.TP
\fB\-W\fR WIDTH, \fB\-\-width\fR WIDTH
Set canvas width
.TP
\fB\-H\fR HEIGHT, \fB\-\-height\fR HEIGHT
Set canvas height
.TP
\fB\-m\fR, \fB\-\-max\fR
Maximum canvas size for terminal (overrides \fB\-W\fR and \fB\-H\fR)
.TP
\fB\-\-nomouse\fR
Disable mouse support
.TP
\fB\-\-cursor\fR CURSOR
Cursor mode (block, underscore, or pipe)
.TP
\fB\-\-notheme\fR
Disable theme support (use default theme)
.TP
\fB\-\-theme\fR THEME
Load a custom theme file
.TP
\fB\-\-cp437\fR
Display extended characters on the screen using Code
Page 437 (IBM\-PC/MS\-DOS) encoding instead of Utf\-8.
(Requires CP437 capable terminal and font) (beta)
.TP
\fB\-\-export\-ansi\fR
Export loaded art to an .ansi file and exit
.TP
\fB\-u\fR UNDOSIZE, \fB\-\-undosize\fR UNDOSIZE
Set the number of undo history states \- default is
100. More requires more RAM, less saves RAM.
.TP
\fB\-V\fR, \fB\-\-version\fR
Show version number and exit
.SH AUTHOR
Durdraw is primarily written by Sam Foster . For a full list of contributors, please see the Github page: https://github.com/cmang/durdraw
durdraw-0.28.0/durdraw.ini 0000664 0000000 0000000 00000001564 14704761225 0015440 0 ustar 00root root 0000000 0000000 ; Durdraw 0.28.0 Configuration File
[Main]
; color-mode sets the color mode to start in. Available options: 16, 256
;color-mode: 16
; disable-mouse.. disablse the mouse.
;disable-mouse: True
; max-canvas atuomatically sets the canvas size to the terminal window size at startup.
;max-canvas: True
; Cursor mode requests a cursor type from the terminal. Available options: block, underscore, pipe
;cursor-mode: underscore
; When scroll-colors is enabled, using the mouse wheel in the canvas changes the
; foreground color instead of moving the cursor.
;scroll-colors: True
[Theme]
; theme-16: ~/.durdraw/themes/purpledrank-16.dtheme.ini
theme-16: ~/.durdraw/themes/mutedchill-16.dtheme.ini
theme-256: ~/.durdraw/themes/mutedform-256.dtheme.ini
;[Charset]
;charset-file-utf8: durdraw/charsets/unicode-legacy-computing.json
;charset-file-cp437: durdraw/charsets/cp437-ibmpc.json
durdraw-0.28.0/durdraw/ 0000775 0000000 0000000 00000000000 14704761225 0014731 5 ustar 00root root 0000000 0000000 durdraw-0.28.0/durdraw/__init__.py 0000664 0000000 0000000 00000000000 14704761225 0017030 0 ustar 00root root 0000000 0000000 durdraw-0.28.0/durdraw/charsets/ 0000775 0000000 0000000 00000000000 14704761225 0016545 5 ustar 00root root 0000000 0000000 durdraw-0.28.0/durdraw/charsets/unicode-groups.xml 0000664 0000000 0000000 00000055302 14704761225 0022237 0 ustar 00root root 0000000 0000000
durdraw-0.28.0/durdraw/durdraw_ansiparse.py 0000664 0000000 0000000 00000050117 14704761225 0021024 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3
# Theory of operation notes:
# A code starts with '\x1B[' or ^[ and ends with a LETTER.
# The ltter is usually 'm' (for Select Graphic Rendition).
# Note that it may end with a letter other than 'm' for some features,
# like cursor movements. For example:
# ^[3A means "move cursor up 3 lines"
# A code looks like this: ^[1;32m for bold color 32. ^[0m for reset.
# ^[0;4;3;42m for reset (0), underline (4), italic (3), background color (42).
# We have a state machine that has attributes. Color, bold, underline, italic, etc.
#
# References:
# Escape code bible: https://en.wikipedia.org/wiki/ANSI_escape_code
# ANSI Sauce (metadata) spec: https://www.acid.org/info/sauce/sauce.htm
import sys
import struct
import re
import pdb
import durdraw.durdraw_movie as durmovie
import durdraw.durdraw_color_curses as dur_ansilib
import durdraw.durdraw_sauce as dursauce
def ansi_color_to_durcolor(ansiColor):
colorName = ansi_color_to_durcolor_table[ansiColor]
durColor = color_name_to_durcolor_table[colorName]
return durColor
ansi_color_to_durcolor_table = {
# foreground
'30': 'Black',
'31': 'Red',
'32': 'Green',
'33': 'Yellow',
'34': 'Blue',
'35': 'Magenta',
'36': 'Cyan',
'37': 'White',
# background
'40': 'Black',
'41': 'Red',
'42': 'Green',
'43': 'Yellow',
'44': 'Blue',
'45': 'Magenta',
'46': 'Cyan',
'47': 'White'
}
color_name_to_durcolor_table = {
'Black': 0,
'Red': 5,
'Green': 3,
'Yellow': 7,
'Blue': 2,
'Magenta': 6,
'Cyan': 4,
'White': 8,
'Black': 00,
'Red': 00,
'Green': 00,
'Yellow': 00,
'Blue': 00,
'Magenta': 00,
'Cyan': 00,
'White': 00
}
def get_width_and_height_of_ansi_blob(text, width=80):
i = 0 # index into the file blob
col_num = 0
line_num = 0
max_col = 0
while i < len(text):
# If there's an escape code, extract data from it
if text[i:i + 2] == '\x1B[': # Match ^[
match = re.search('[a-zA-Z]', text[i:]) # match any control code
end_index = match.start() + i # where the code ends
if text[end_index] == 'A': # Move the cursor up X spaces
escape_sequence = text[i + 2:end_index]
if len(escape_sequence) == 0:
escape_sequence = 1
move_by_amount = int(escape_sequence)
line_num = line_num - move_by_amount
i = end_index + 1
continue # jump the while
elif text[end_index] == 'B': # Move the cursor down X spaces
escape_sequence = text[i + 2:end_index]
if len(escape_sequence) == 0:
escape_sequence = 1
move_by_amount = int(escape_sequence)
line_num += move_by_amount
i = end_index + 1
continue # jump the while
elif text[end_index] == 'C': # Move the cursor forward X spaces
escape_sequence = text[i + 2:end_index]
if len(escape_sequence) == 0:
escape_sequence = 1
move_by_amount = int(escape_sequence)
col_num += move_by_amount
i = end_index + 1
continue # jump the while
elif text[end_index] == 'D': # Move the cursor back X spaces
escape_sequence = text[i + 2:end_index]
if len(escape_sequence) == 0:
escape_sequence = 1
move_by_amount = int(escape_sequence)
col_num = col_num - move_by_amount
i = end_index + 1
continue # jump the while
elif text[end_index] == 'H': # Move the cursor to row/column
escape_sequence = text[i + 2:end_index]
escape_codes = escape_sequence.split(';')
if len(escape_codes) > 1: # row ; column
if escape_codes[0].isnumeric():
line_num = int(escape_codes[0])
if escape_codes[1].isnumeric():
col_num = int(escape_codes[1])
elif len(escape_codes) == 1: # row, column=1
#line_num = 1
if escape_codes[0].isnumeric():
col_num = int(escape_codes[0])
i = end_index + 1
continue # jump the while
elif text[end_index] == 'J': # Clear screen
# 0 or none = clear from cursor to end of screen
# 1 = from cursor to top of screen
# 2 = clear screen and move cursor to top left
# 3 = clear entire screen and delete all lines saved in the scrollback buffer
escape_sequence = text[i + 2:end_index]
if len(escape_sequence) == 0:
escape_sequence = '0' # default - clear from cursor to end
if escape_sequence == '2': # cls, move to top left
# using a sledgehammer to claer the screen
#new_frame = durmovie.Frame(width, height + 1)
col_num, line_num = 0, 0
i = end_index + 1 # move on to next byte
continue
elif text[end_index] == 's': # save current position/state
saved_col_num = col_num
saved_line_num = line_num
elif text[end_index] == 'u': # restore saved position/state
col_num = saved_col_num
line_num = saved_line_num
i = end_index + 1
continue # jump the while
# Or, not an escape character
elif text[i] == '\n': # new line (LF)
line_num += 1
if col_num > max_col:
max_col = col_num
col_num = 0
elif text[i] == '\r': # windows style newline (CR)
pass # pfft
elif text[i:i + 5] == 'SAUCE' and len(text) - i == 128: # SAUCE record found
i += 128
# Wee, I'm flying
else: # printable character (hopefully)
if col_num == width:
col_num = 0
line_num += 1
character = text[i]
character = text[i]
col_num += 1
i += 1
print("")
width = max_col
height = line_num
return width, height
def parse_ansi_escape_codes(text, filename = None, appState=None, caller=None, console=False, debug=False, maxWidth=80):
""" Take an ANSI file blob, load it into a DUR frame object, return
frame """
if filename:
# If we can just pull it from the Sauce, cool
sauce = dursauce.SauceParser()
sauce.parse_file(filename)
if sauce.sauce_found:
appState.sauce = sauce
#if sauce.height > 0 and sauce.width > 0:
#if sauce.height == None:
# sauce.height = 25
if sauce.width == None:
#sauce.width = 80
sauce.width = maxWidth
maxWidth = sauce.width
width = sauce.width
height = sauce.height
#caller.notify(f"Sauce pulled: author: {sauce.author}, title: {sauce.title}, width {width}, height {height}")
if not sauce.height:
width, height = get_width_and_height_of_ansi_blob(text)
width = sauce.width
if not sauce.sauce_found or width > 200 or height > 1200: # let the dodgy function guess
width, height = get_width_and_height_of_ansi_blob(text)
width = max(width, maxWidth)
#width = max(width, 80)
height += 1
if appState.debug:
caller.notify(f"Guessed width: {width}, height: {height}")
#width = min(width, maxWidth)
height = max(height, 25)
new_frame = durmovie.Frame(width, height + 1)
if appState.debug:
caller.notify(f"debug: maxWidth = {maxWidth}")
#parsed_text = ''
#color_codes = ''
i = 0 # index into the file blob
col_num = 0
line_num = 0
max_col = 0
default_fg_color = appState.defaultFgColor
default_bg_color = appState.defaultBgColor
fg_color = default_fg_color
bg_color = default_bg_color
bold = False
saved_col_num = 0
saved_line_num = 0
saved_byte_location = 0
parse_error = False
while i < len(text):
# If there's an escape code, extract data from it
if text[i:i + 2] == '\x1B[': # Match ^[[
match = re.search('[a-zA-Z]', text[i:]) # match any control code
end_index = match.start() + i # where the code ends
if text[end_index] == 'm': # Color/SGR control code
escape_sequence = text[i + 2:end_index]
escape_codes = escape_sequence.split(';')
codeList = []
for code in escape_codes:
try:
codeList.append(int(code))
except:
if caller:
pass
#caller.notify(f"Error in byte {i}, char: {code}, line: {line_num}, col: {col_num}")
if len(codeList) > 1 and appState.colorMode == "256":
# 256 foreground color
bg_color = default_bg_color
if codeList[0] == 38 and codeList[1] == 5 and len(codeList) == 3:
fg_color = codeList.pop()
codeList = [fg_color]
# 256 background color
elif codeList[0] == 48 and codeList[1] == 5 and len(codeList) == 3:
bg_color = codeList.pop()
codeList = [fg_color]
# Not a 256 color code - treat as 16 color
for code in codeList:
if code == 0: # reset
fg_color = default_fg_color
bg_color = default_bg_color
bold = False
if code == 1: # bold
bold = True
# In case we're still using a previous color with new attributes
if fg_color < 9: # sledgehammer
if bold:
fg_color += 8
# 16 Colors
if code > 29 and code < 38: # FG colors 0-8, or 30-37
if bold:
code += 60 # 30 -> 90, etc, for DOS-style bright colors that use bold
#bold = False
if appState.colorMode == "256":
fg_color = dur_ansilib.ansi_code_to_dur_16_color[str(code)]
else:
#if bold:
# code += 60 # 30 -> 90, etc, for DOS-style bright colors that use bold
fg_color = dur_ansilib.ansi_code_to_dur_16_color[str(code)]
#if bold:
# fg_color += 8
# fix for durdraw color pair stupidity
if fg_color == -1 or fg_color == 0: # black fg and bright black fg fix
if bold:
fg_color = 9
else:
fg_color = 1
#if fg_color == 8: # bright white fix
# if bold:
# fg_color = 16
#bold = False
if fg_color < 9: # sledgehammer
if bold:
fg_color += 8
elif code > 39 and code < 48: # BG colors 0-8, or 40-47
if appState.colorMode == "256":
#bg_color = dur_ansilib.ansi_code_to_dur_16_color[str(code)] - 1
#bg_color = 0
bg_color = dur_ansilib.ansi_code_to_dur_16_color[str(code)] - 1
if bg_color == -1:
bg_color = 0
else:
#if bold:
# code += 60 # 30 -> 90, etc, for DOS-style bright colors that use bold
bg_color = dur_ansilib.ansi_code_to_dur_16_color[str(code)] - 1
if bg_color == -1:
bg_color = 0
if fg_color == -1 or fg_color == 0: # black fg and bright black fg fix
if bold:
fg_color = 9
else:
fg_color = 1
# 256 Colors
if console:
print(str(escape_codes), end="")
# Add color to color map
try:
new_frame.newColorMap[line_num][col_num] = [fg_color, bg_color]
except Exception as E:
if console:
print(str(E))
print(f"line num: {line_num}")
i = end_index + 1
continue # jump the while
elif text[end_index] == 'A': # Move the cursor up X spaces
escape_sequence = text[i + 2:end_index]
if len(escape_sequence) == 0:
escape_sequence = 1
move_by_amount = int(escape_sequence)
line_num = line_num - move_by_amount
i = end_index + 1
continue # jump the while
elif text[end_index] == 'B': # Move the cursor down X spaces
escape_sequence = text[i + 2:end_index]
if len(escape_sequence) == 0:
escape_sequence = 1
move_by_amount = int(escape_sequence)
line_num += move_by_amount
i = end_index + 1
continue # jump the while
elif text[end_index] == 'C': # Move the cursor forward X spaces
escape_sequence = text[i + 2:end_index]
if len(escape_sequence) == 0:
escape_sequence = 1
move_by_amount = int(escape_sequence)
if col_num >= maxWidth:
col_num = 0
line_num += 1
col_num += move_by_amount
i = end_index + 1
continue # jump the while
elif text[end_index] == 'D': # Move the cursor back X spaces
escape_sequence = text[i + 2:end_index]
if len(escape_sequence) == 0:
escape_sequence = 1
move_by_amount = int(escape_sequence)
col_num = col_num - move_by_amount
i = end_index + 1
continue # jump the while
elif text[end_index] == 'H': # Move the cursor to row/column
escape_sequence = text[i + 2:end_index]
escape_codes = escape_sequence.split(';')
if len(escape_codes) > 1: # row ; column
if escape_codes[0].isnumeric():
line_num = int(escape_codes[0])
if escape_codes[1].isnumeric():
col_num = int(escape_codes[1])
elif len(escape_codes) == 1: # row, column=1
#line_num = 1
if escape_codes[0].isnumeric():
col_num = int(escape_codes[0])
i = end_index + 1
continue # jump the while
elif text[end_index] == 'J': # Clear screen
# 0 or none = clear from cursor to end of screen
# 1 = from cursor to top of screen
# 2 = clear screen and move cursor to top left
# 3 = clear entire screen and delete all lines saved in the scrollback buffer
escape_sequence = text[i + 2:end_index]
if len(escape_sequence) == 0:
escape_sequence = '0' # default - clear from cursor to end
if escape_sequence == '2': # cls, move to top left
# using a sledgehammer to claer the screen
new_frame = durmovie.Frame(width, height + 1)
col_num, line_num = 0, 0
i = end_index + 1 # move on to next byte
continue
elif text[end_index] == 's': # save current position/state
#saved_col_num = col_num
#saved_line_num = line_num
i = end_index + 1 # move on to next byte
#saved_byte_location = i
continue
elif text[end_index] == 'u': # restore saved position/state
#col_num = saved_col_num
#line_num = saved_line_num
#i = saved_byte_location
i = end_index + 1 # move on to next byte
#pdb.set_trace()
continue
else: # Some other escape code, who cares for now
if appState.debug:
caller.notify(f"Unknown escape character type encountered: {text[end_index]}")
i = end_index + 1 # move on to next byte
continue
# Or, not an escape character
elif text[i] == '\n': # new line (LF)
line_num += 1
if col_num > max_col:
max_col = col_num
col_num = 0
elif text[i] == '\r': # windows style newline (CR)
pass # pfft
elif text[i] == '\x00': # Null byte
pass
elif text[i] == '\x1a': # ctl-z code, EOF, just before the SAUCE
pass
#elif text[i] == '\x01': # CTRL-A, SOH (start header).
# # Q: Why is this in some ANSIs? A: Because it's a smiley face in CP437
# pass
#elif text[i] == '\x02': # CTRL-B, STX (start text)
# pass
elif text[i:i + 5] == 'SAUCE' and len(text) - i == 128: # SAUCE record found
i += 128
else: # printable character (hopefully)
if col_num >= maxWidth:
col_num = 0
line_num += 1
character = text[i]
try:
new_frame.content[line_num][col_num] = character
except IndexError:
parse_error = True
if debug:
caller.notify(f"Error writing content. Width: {width}, Height: {height}, line: {line_num}, col: {col_num}, char: {character}, pos: {i}")
try:
new_frame.newColorMap[line_num][col_num] = [fg_color, bg_color]
except IndexError:
parse_error = True
if debug:
caller.notify(f"Error writing color. Width: {width}, Height: {height}, line: {line_num}, col: {col_num}, pos: {i}")
if console:
print(character, end='')
col_num += 1
i += 1
if console:
print("")
print(f"Lines: {line_num}, Columns: {max_col}")
if parse_error:
caller.notify(f"Possible errors detected while loading this file. It may not display correctly.")
height = line_num
width = max_col
frame = durmovie.Frame(height, width)
line_num = 0
col_num = 0
# maybe usethis for the color: dur_ansilib.ansi_code_to_dur_16_color[fg_ansi]
return new_frame
if __name__ == "__main__":
# Example usage
#file_path = 'kali.ans'
#file_path = '11.ANS'
file_path = '../rainbow.ans'
if len(sys.argv) > 1:
file_path = sys.argv[1]
with open(file_path, 'r') as file:
try:
text_with_escape_codes = file.read()
except UnicodeDecodeError:
file.close()
file = open(file_path, "r", encoding="cp437")
#file = open(file_path, "r", encoding="big5")
text_with_escape_codes = file.read()
#parsed_text, fg, bg = parse_ansi_escape_codes(text_with_escape_codes)
newFrame = parse_ansi_escape_codes(text_with_escape_codes, console=True)
print(str(newFrame.newColorMap))
print(str(newFrame.content))
print(str(newFrame))
#print(parsed_text)
#print(f"Fg: {fg}, bg: {bg}")
durdraw-0.28.0/durdraw/durdraw_appstate.py 0000664 0000000 0000000 00000040423 14704761225 0020657 0 ustar 00root root 0000000 0000000 import configparser
import curses
import gzip
import os
import pdb
import pickle
import subprocess
import sys
import threading
from sys import version_info
from durdraw.durdraw_options import Options
import durdraw.durdraw_file as durfile
import durdraw.durdraw_sauce as dursauce
class AppState():
""" run-time app state, separate from movie options (Options()) """
def __init__(self):
# Check for optional dependencies
#self.ansiLove = self.isAppAvail("ansilove")
#self.neofetch = self.isAppAvail("neofetch")
#self.PIL = self.checkForPIL()
self.ansiLove = None
self.neofetch = None
self.PIL = None
self.check_dependencies()
# User friendly defeaults
self.quickStart = False
self.mental = False # Mental mode - enable experimental options/features
self.showStartupScreen = False
self.narrowWindow = False
self.curOpenFileName = ""
python_version = f"{version_info.major}.{version_info.minor}.{version_info.micro}"
self.pyVersion = python_version
self.colorMode = "256" # or 16, or possibly "none" or "true" or "rgb" (24 bit rgb "truecolor")
self.fileColorMode = None
self.maxColors = 256
self.iceColors = False
self.can_inject = False # Allow injecting color codes to override ncurses colors (for BG 256 colors)
self.showBgColorPicker = False # until BG colors work in 256 color mode. (ncurses 5 color pair limits)
self.scrollColors = False # When true, scroll wheel in canvas changes color instead of moving cursor
self.editorRunning = True
self.screenCursorMode = "default" # can be block, underscore, pipe
self.renderMouseCursor = False # show Paint or Draw cursor in canvas
self.validScreenCursorModes = ["default", "block", "underscore", "pipe"]
self.cursorBlinks = True # lord help me, why would anyone not want this to be true?
self.totalFgColors = 16
self.totalBgColors = 8
self.defaultFgColor = 7
self.defaultBgColor = 0
self.width = 80 # default canvas width/columns
self.height = 23 # default canvas height/lines
self.wrapWidth = 80 # Default width to wrap at when loading ASCII files (.asc/.txt)
self.minWindowWidth = 40 # smaller than this, and Durdraw complains that it can't draw the UI
self.full_ui_width = 80 # smaller than this, and draw the streamlined UI
self.stickyColorPicker = True # true to keep color picker on screen
self.colorPickerSelected = False # true when the user hits esc-c
self.charEncoding = 'utf-8' # or cp437, aka ibm-pc
self.unicodeBlockList = []
self.characterSet = "Durdraw Default"
self.showCharSetButton = False
self.workingLoadDirectory = None
self.fileShortPath = None
self.fileLongPath = None
# if self.characterSet == "Unicode Block" then Durdraw knows to use a
# unicode block:
#self.characterSet = "Unicode Block"
self.unicodeBlock = "Braille Patterns" # placeholder during initialization
self.cursorMode = "Move" # Move/Select, Draw and Color
self.fetchMode = False # use neofetch, replace {variables} in dur file
self.fetchData = None # a {} dict containing key:value for neofetch output.
self.inferno = None
self.inferno_opts = None
self.playOnlyMode = False # This means viewer mode now, actually..
self.viewModeShowInfo = False # show sauce etc in view mode
self.playNumberOfTimes = 0 # 0 = loop forever, default
self.undoHistorySize = 100 # How far back our undo history can
self.playbackRange = (1,1)
self.drawChar = '$'
self.brush = None
self.configFile = None
self.configFileLoaded = False
self.configFileName = None
self.customThemeFile = None
self.sauce = dursauce.SauceParser() # empty sauce
#self.drawChar = b'\xE2\x96\x88'
self.CP438_BLOCK = chr(219)
self.UTF8_BLOCK = chr(9608)
self.blockChar = self.UTF8_BLOCK # Unicode block by default, --cp437 should change this
self.colorPickChar = self.blockChar
self.hasMouse = True # replace with equivalent curses.has_mouse()
self.hasMouseScroll = True # Disable for compatibility with older Python versions <3.10
self.mouse_col = 0
self.mouse_line = 0
self.helpMov = None
self.helpMov_2 = None
self.hasHelpFile = False
self.playingHelpScreen = False
self.playingHelpScreen_2 = False # on page 2 of help screen
self.durVer = None
self.debug = False
self.debug2 = False # extra verbose debug, eg: file loading intricates
self.modified = False
self.durhelp256_fullpath = None
self.durhelp256_page2_fullpath = None
self.durhelp16_fullpath = None
self.durhelp16_page2_fullpath = None
# This doesn't work yet (color pairs past 256 colors. They set, but the background color doesn't get set.
#if sys.version_info >= (3, 10):
# if curses.has_extended_color_support(): # Requires Ncures 6
# self.showBgColorPicker = True # until BG colors work in 256 color mode. (ncurses 5 color pair limits)
self.realmaxX = 0
self.realmaxY = 0
self.topLine = 0 # the top line visible on the screen, used in refresh() for scrolling
self.firstCol = 0 # leftmost visbile column, to facilitate left/right scrolling
self.drawBorders = True
self.durFileVer = 0 # gets set in main() from DUR_FILE_VER
self.sideBarEnabled = True # to show color picker, sauce info, etc
self.sideBarColumn = 0 # location, usually just right of the border
#self.sideBar_minimum_width = 37 # Must have this much width to draw sidebar. Actually it's the colorBar width.
self.sideInfo_minimum_width = 8 # Must have this much width beyond canvas width to draw esc-i sauce info
self.sideBar_minimum_width = 5 # Must have this much width to draw sidebar. Actually it's the colorBar width.
self.sideBar_minimum_width_256 = 37 # Must have this much width to draw sidebar. Actually it's the colorBar width.
self.sideBar_minimum_width_16 = 12 # Must have this much width to draw sidebar. Actually it's the colorBar width.
self.bottomBar_minimum_height = 10 # same as above, but for height
self.bottomBar_minimum_height_256 = 10
self.bottomBar_minimum_height_16 = 4
self.colorBar_height = 8
self.sideBarShowing = False
self.themesEnabled = True
self.themeName = "default"
self.theme_16 = {
'mainColor': 8, # grey
'clickColor': 3, # green
'clickHighlightColor': 11, # bright green
'borderColor': 8, # grey
#'notifications': 89 # sick maroon
'notificationColor': 8, # grey
'promptColor': 8, # grey
'menuItemColor': 2,
'menuTitleColor': 3,
'menuBorderColor': 4,
}
self.theme_256 = {
'mainColor': 7, # grey
'clickColor': 2, # green
'clickHighlightColor': 10, # bright green
'borderColor': 7, # grey
#'notifications': 89 # sick maroon
'notificationColor': 3, # cyan
'promptColor': 3, # cyan
'menuItemColor': 7,
'menuTitleColor': 98,
'menuBorderColor': 7,
}
self.theme = self.theme_16
def maximize_canvas(self):
term_size = os.get_terminal_size()
if term_size[0] > 80:
self.width = term_size[0]
if term_size[1] > 24:
self.height = term_size[1] - 2
def check_dependencies(self):
dependency_thread = threading.Thread(target=self.thread_check_dependencies)
dependency_thread.start()
def thread_check_dependencies(self):
# Check for optional dependencies
self.ansiLove = self.isAppAvail("ansilove")
self.neofetch = self.isAppAvail("neofetch")
self.PIL = self.checkForPIL()
def setCursorModeMove(self):
self.cursorMode="Move"
def setCursorModeSelect(self):
self.cursorMode="Select"
def setCursorModeDraw(self):
self.cursorMode="Draw"
def setCursorModePaint(self):
self.cursorMode="Paint"
def setCursorModeCol(self):
self.cursorMode="Color"
def setCursorModeErase(self):
self.cursorMode="Erase"
def setCursorModeEyedrop(self):
self.cursorMode="Eyedrop"
def setDurFileVer(self, durFileVer): # file format for saving. 1-4 are pickle, 5+ is JSON
self.durFileVer = durFileVer
def setDurVer(self, version):
self.durVer = version
def setDebug(self, isEnabled: bool):
self.debug = isEnabled
def loadThemeList(self):
""" Look for theme files in internal durdraw directory """
# durhelp256_fullpath = pathlib.Path(__file__).parent.joinpath("help/durhelp-256-long.dur")
# Get a list of files from the themes paths
internal_theme_path = pathlib.Path(__file__).parent.joinpath("themes/")
self.internal_theme_file_list = glob.glob(f"{internal_theme_path}/*.dtheme.ini")
#user_theme_path = pathlib.Path(__file__).parent.joinpath("themes/")
#self.user_theme_file_list = glob.glob(f"{user_theme_path}/*.dtheme.ini")
# Turn lists into an index of Theme name, Theme type, and Path to
available_themes = [] # populate with a list of dicts containing name=, path=, type=
for filename in self.internal_theme_file_list:
pass
def loadConfigFile(self):
# Load configuration filea
configFullPath = os.path.expanduser("~/.durdraw/durdraw.ini")
configShortFile = 'durdraw.ini'
configFileLocations = [configFullPath, configShortFile]
configFile = configparser.ConfigParser()
readConfigPaths = configFile.read(configFileLocations)
if self.configFile == []:
self.configFileLoaded = False
return False
else:
self.configFileName = readConfigPaths
self.configFile = configFile
self.configFileLoaded = True
return True
def loadThemeFromConfig(self, themeMode):
#pdb.set_trace()
if not self.themesEnabled:
return False
if 'Theme' in self.configFile:
themeConfig = self.configFile['Theme']
if 'theme-16' in themeConfig and themeMode == 'Theme-16':
self.loadThemeFile(themeConfig['theme-16'], themeMode)
if 'theme-256' in themeConfig and themeMode == 'Theme-256':
self.loadThemeFile(themeConfig['theme-256'], themeMode)
def getConfigOption(self, section: str, item: str):
# section = something like [Main], item = something like color-mode:
try: # see if section and item exist, otherwise return False
configSection = self.configFile[section]
configItem = configSection[item]
return configItem
except KeyError:
return False
def loadThemeFile(self, themeFilePath, themeMode):
# If there is a theme set, use it
#if 'Theme' in self.configFile:
# Load .dtheme file
#themeFullPath = os.path.expanduser(f"~/.durdraw/{themeName}.dtheme")
themeFullPath = os.path.expanduser(themeFilePath)
themeFileConfig = configparser.ConfigParser()
themeConfigsLoaded = themeFileConfig.read(themeFullPath)
#pdb.set_trace()
if themeConfigsLoaded == []:
return False # could not find or load the theme file
else:
theme = themeFileConfig[themeMode]
if 'name' in theme:
self.themeName = str(theme['name'])
if 'mainColor' in theme:
self.theme['mainColor'] = int(theme['mainColor'])
if 'clickColor' in theme:
self.theme['clickColor'] = int(theme['clickColor'])
if 'borderColor' in theme:
self.theme['borderColor'] = int(theme['borderColor'])
if 'clickHighlightColor' in theme:
self.theme['clickHighlightColor'] = int(theme['clickHighlightColor'])
if 'notificationColor' in theme:
self.theme['notificationColor'] = int(theme['notificationColor'])
if 'promptColor' in theme:
self.theme['promptColor'] = int(theme['promptColor'])
if 'menuItemColor' in theme:
self.theme['menuItemColor'] = int(theme['menuItemColor'])
if 'menuTitleColor' in theme:
self.theme['menuTitleColor'] = int(theme['menuTitleColor'])
if 'menuBorderColor' in theme:
self.theme['menuBorderColor'] = int(theme['menuBorderColor'])
return True
def checkForPIL(self):
try:
import PIL
return True
except ImportError:
return False
def isAppAvail(self, name): # looks for program 'name' in path
try:
devnull = open(os.devnull)
subprocess.Popen([name], stdout=devnull, stderr=devnull).communicate()
except OSError as e:
#if e.errno == os.errno.ENOENT:
# return False
return False
return True
def loadDurFileToMov(self, fileName):
""" Takes a file path, returns a movie object """
fileName = os.path.expanduser(fileName)
#self.helpMov = Movie(self.opts) # initialize a new movie to work with
try:
f = open(fileName, 'rb')
except Exception as e:
return False
if (f.read(2) == b'\x1f\x8b'): # gzip magic number
# file == gzip compressed
f.close()
try:
f = gzip.open(fileName, 'rb')
except Exception as e:
return False
else:
f.seek(0)
try: # Load json help file
#pdb.set_trace()
loadedContainer = durfile.open_json_dur_file(f, self)
opts = loadedContainer['opts']
mov = loadedContainer['mov']
return mov, opts
except:
#pass # loading json help file failed for some reason, so...
return False
def loadHelpFileThread(self, helpFileName):
help_loading_thread = threading.Thread(target=self.loadHelpFile, args=(helpFileName,))
help_loading_thread.start()
def loadHelpFile(self, helpFileName, page=1):
helpFileName = os.path.expanduser(helpFileName)
#self.helpMov = Movie(self.opts) # initialize a new movie to work with
try:
f = open(helpFileName, 'rb')
except Exception as e:
self.hasHelpFile = False
self.helpMov = None
return False
if (f.read(2) == b'\x1f\x8b'): # gzip magic number
# file == gzip compressed
f.close()
try:
f = gzip.open(helpFileName, 'rb')
except Exception as e:
self.hasHelpFile = False
self.helpMov = None
self.helpMov_2 = None
return False
else:
f.seek(0)
try: # Load json help file
#pdb.set_trace()
loadedContainer = durfile.open_json_dur_file(f, self)
if page == 1:
self.helpMovOpts = loadedContainer['opts']
self.helpMov = loadedContainer['mov']
elif page == 2:
self.helpMovOpts_2 = loadedContainer['opts']
self.helpMov_2 = loadedContainer['mov']
self.hasHelpFile = True
return True
except:
#pass # loading json help file failed for some reason, so...
return False
#try: # Load pickle file. This should never happen anymore, so...
# #self.opts = pickle.load(f)
# #self.mov = pickle.load(f)
# self.helpMovOpts = pickle.load(f)
# self.helpMov = pickle.load(f)
# self.hasHelpFile = True
# return True
#except Exception as e:
# self.hasHelpFile = False
# self.helpMov = None
# return False
durdraw-0.28.0/durdraw/durdraw_charsets.py 0000664 0000000 0000000 00000010313 14704761225 0020645 0 ustar 00root root 0000000 0000000 # Load character sets from files, initialize them
import pdb
import pathlib
import xml.etree.ElementTree as ET
def hex_range_iterator(start_hex, end_hex):
""" Iterator that takes a first and last hex number, and returns each one
in succession.
Used to generate character sets from unicode-provided ranges
# Example usage:
start_hex = "1FB00"
end_hex = "1FBFF"
for hex_value in hex_range_iterator(start_hex, end_hex):
print(hex_value)
"""
start_int = int(start_hex, 16) # Convert start_hex to an integer
end_int = int(end_hex, 16) # Convert end_hex to an integer
current_int = start_int
while current_int <= end_int:
#yield format(current_int, 'X') # Convert the integer back to hexadecimal
#yield hex(current_int)
yield current_int
current_int += 1
def parse_xml_blocks_file(xml_file_path):
""" laod XML file, returns block_data object, containing all the block
names and their first and last code point (hex value)
"""
block_data = {}
# Parse the XML file
tree = ET.parse(xml_file_path)
root = tree.getroot()
# Iterate through the 'block' elements and extract the data
for block_element in root.findall('.//block'):
first_cp = block_element.get('first-cp')
last_cp = block_element.get('last-cp')
block_name = block_element.get('name')
if block_name is not None and first_cp is not None and last_cp is not None:
block_data[block_name] = {
'first-cp': first_cp,
'last-cp': last_cp
}
return block_data
# We can return a fullCharMap that looks like this: [ {'f1':\x1FB00,
# 895 self.fullCharMap = [ \
# 896 # All of our unicode templates live here. Blank template:
# 897 #{'f1':, 'f2':, 'f3':, 'f4':, 'f5':, 'f6':, 'f7':, 'f8':, 'f9':, 'f10':},
# 898
# 899 # block characters
# 900 {'f1':9617, 'f2':9618, 'f3':9619, 'f4':9608, 'f5':9600, 'f6':9604, 'f7':9612, 'f8':9616, 'f9': 9632, 'f10':183 }, # ibm-pc looking block characters (but unicode instead of ascii)
def load_unicode_block(block_name: str):
""" returns a fullCharMap """
#xml_block_filename = 'unicode-groups.xml'
xml_block_filename = pathlib.Path(__file__).parent.joinpath("charsets/unicode-groups.xml")
block_data = parse_xml_blocks_file(xml_block_filename)
if block_name in block_data:
block_info = block_data[block_name]
#print(f"Block: {block_name}")
#print(f"First Code Point: {block_info['first-cp']}")
#print(f"Last Code Point: {block_info['last-cp']}")
first_cp = block_info['first-cp']
last_cp = block_info['last-cp']
charMap = []
fKeyList = {'f1':'', 'f2':'', 'f3':'', 'f4':'', 'f5':'', 'f6':'', 'f7':'', 'f8':'', 'f9':'', 'f10':''}
fKeyNum = 1
# go through each code point, assign them to F1-F10, and add those f1-f10
# sets to the larger full character map
for code_point in hex_range_iterator(first_cp, last_cp):
#fKeyList[f'f{fKeyNum}'] = chr(code_point)
#fKeyList[f'f{fKeyNum}'] = f'\U{hex(code_point)}'
fKeyList[f'f{fKeyNum}'] = code_point
if fKeyNum == 10:
charMap.append(fKeyList.copy())
fKeyNum = 1
else:
fKeyNum += 1
#print(str(fKeyList))
return charMap
else:
#print(f"Block '{block_name}' not found in the XML data.")
# this should not happen, so...
return None
def get_unicode_blocks_list():
xml_block_filename = pathlib.Path(__file__).parent.joinpath("charsets/unicode-groups.xml")
block_data = parse_xml_blocks_file(xml_block_filename)
block_list = list(block_data.keys())
block_list = block_list[1:] # cut off Basic Latin, or 0000-007F, as 0000 is a null character
return block_list
if __name__ == "__main__":
#charMap = load_unicode_block("Chess Symbols")
#charMap = load_unicode_block("Symbols for Legacy Computing")
#charMap = load_unicode_block("Emoticons")
#print(str(charMap))
unicodeBlocksList = get_unicode_blocks_list()
print(str(unicodeBlocksList))
durdraw-0.28.0/durdraw/durdraw_color_curses.py 0000664 0000000 0000000 00000130102 14704761225 0021532 0 ustar 00root root 0000000 0000000 import curses
import time
import pdb
legacy_256_to_256 = { # used when loading an old 256 color file in 256 color mode
#0: 7, # white
1: 7, # white
2: 3, # cyan
3: 5, # magenta
4: 1, # blue
5: 6, # yellow/brown
6: 2, # green
7: 4, # red
8: 16, # black
9: 12, # bright red
10: 10, # bright green
11: 14, # bright yellow
12: 9, # bright blue
13: 13, # bright magenta
14: 11, # bright cyan
15: 15, # bright white
16: 8, # bright black
}
legacy_16_to_256 = { # used when loading an old 16 color file in 256 color mode
#0: 7, # white
1: 7, # white
2: 3, # cyan
3: 5, # magenta
4: 1, # blue
5: 6, # yellow/brown
6: 2, # green
7: 4, # red
8: 16, # black
9: 15, # bright white
10: 11, # bright cyan
11: 13, # bright magenta
12: 9, # bright blue
13: 14, # bright yellow
14: 10, # bright green
15: 12, # bright red
16: 8, # bright black
}
legacy_16_to_16 = { # used when loading an old 16 color file in 16 color mode
#0: 7, # white
1: 8, # white
2: 4, # cyan
3: 6, # magenta
4: 2, # blue
5: 7, # yellow/brown
6: 3, # green
7: 5, # red
8: 1, # black
9: 16, # bright white
10: 12, # bright cyan
11: 14, # bright magenta
12: 10, # bright blue
13: 15, # bright yellow
14: 11, # bright green
15: 13, # bright red
16: 9, # bright black
'bg': { # background colors
1: 7,
2: 3, # cyan
3: 5, # magenta
4: 1, # blue
5: 6, # yellow/brown
6: 2, # green
7: 4, # red
8: 8, # black
}
}
color_256_to_ansi_16 = { # used when saving a 256 color file, to convert first 16 colors
#0: 7, # white
1: 4, # blue
2: 2, # green
3: 6, # cyan
4: 1, # red
5: 5, # magenta
6: 3, # yellow
7: 7, # white
8: 8, # bright black
9: 12, # bright blue
10: 10, # bright green
11: 14, # bright cyan
12: 9, # bright red
13: 13, # bright magenta
14: 11, # bright yellow
15: 15, # bright white
16: 16, # black
}
mirc_16_to_color_16 = {
0: 0, # white
1: 1, # black
2: 2, # blue
3: 3, # green
10: 4, # cyan
5: 5, # red (brown)
6: 6, # magenta
7: 7, # yellow (orange)
15: 8, # white/grey
14: 9, # br black/dark grey
12: 10, # br blue
9: 11, # br green
11: 12, # br cyan
4: 13, # br red
13: 14, # br magenta
8: 15, # br yellow
#0: 16, # br white
}
color_16_to_mirc_16 = {
0: 0, # white
1: 1, # black
2: 2, # blue
3: 3, # green
4: 10, # cyan
5: 5, # red (brown)
6: 6, # magenta
7: 7, # yellow (orange)
8: 15, # white/grey
9: 14, # br black/dark grey
10: 12, # br blue
11: 9, # br green
12: 11, # br cyan
13: 4, # br red
14: 13, # br magenta
15: 8, # br yellow
16: 0, # br white
}
color_256_to_mirc_16 = {
0: 0, # white
1: 1, # black
2: 2, # blue
3: 3, # green
4: 10, # cyan
5: 5, # red (brown)
6: 6, # magenta
7: 7, # yellow (orange)
8: 15, # white/grey
9: 14, # br black/dark grey
10: 12, # br blue
11: 9, # br green
12: 11, # br cyan
13: 4, # br red
14: 13, # br magenta
15: 8, # br yellow
16: 0, # br white
}
ansi_code_to_dur_16_color = {
'30': 0, # black
'31': 5, # red
'32': 3, # green
'33': 7, # yellow/brown
'34': 2, # blue
'35': 6, # magenta
'36': 4, # cyan
'37': 8, # grey/white
'90': 9, # bright black?
'91': 13, # bright red?
'92': 11, # bright green?
'93': 15, # bright yellow
'94': 10, # bright blue
'95': 14, # bright magenta
'96': 12, # bright cyan
'97': 16, # bright white
'40': 0, # black
'41': 5, # red
'42': 3, # green
'43': 7, # yellow/brown
'44': 2, # blue
'45': 6, # magenta
'46': 4, # cyan
'47': 8, # grey/white
}
#ansi_code_to_dur_16_color = {
# '30': 0, # black
# '31': 8, # white # red
# '32': 7, # yellow/brown # green
# '33': 6, # magenta # yellow/brown
# '34': 5, # red # blue
# '35': 4, # cyan # magenta
# '36': 3, # green # cyan
# '37': 2, # blue # grey/white
#}
class AnsiArtStuff():
""" Ansi specific stuff.. escape codes, any refs to code page 437, ncurses
color boilerplate, etc """
def __init__(self, appState):
self.appState = appState
self.colorPairMap = None # fill this with dict of FG/BG -> curses pair #
self.escapeFgMap = { # color numbers documented in initColorPairs() comments
# ANSI escape code FG colors
# regular colors, white (1) through to black (8 and 0)
# Using aciddraw/pablodraw-style color order
0:"0;30", # 1: black
1:"0;30", # 1: black
2:"0;34", # 2: blue
3:"0;32", # 3: green
4:"0;36", # 4: cyan
5:"0;31", # 5: red
6:"0;35", # 6: magenta
7:"0;33", # 7: yellow/brown
8:"0;37", # 8: grey/white
# bright colors
9:"1;30", # 9: dark grey/black
10:"1;34", # 10 bright blue
11:"1;32", # 11 bright green
12:"1;36", # cyan
13:"1;31", # bright red
14:"1;35", # magenta
15:"1;33", # yellow
16:"1;37", # Bright white
}
self.escapeFgMap_old = { # color numbers documented in initColorPairs() comments
# ANSI escape code FG colors
# regular colors, white (1) through to black (8 and 0)
1:"0;37", 2:"0;36", 3:"0;35", 4:"0;34",
5:"0;33", 6:"0;32", 7:"0;31", 8:"0;30", 0:"0;30",
# bright colors, brwhite (9) through to brblack (16)
9:"1;37", 10:"1;36", 11:"1;35", 12:"1;34",
13:"1;33", 14:"1;32", 15:"1;31", 16:"1;30"
}
self.ansiGraphicsModeTokenMap = {
""" For parsing ANSI graphics sequence. Eg: ^[0;32;42m is
green fg (32), magenta bg (42) no bold (0), setting graphics
mode (m) """
# expects a function to extract escape sequence then tokenize
# based on locations of ^[, ; and ending with m.
# Should this store logical color name strings instead of
# durdraw color numbers? Then another map can convert names
# to numbers.
# * Other ANSI gotchas: Pablodraw uses Cursor Forward (C)
# commands before each block of characters. Eg: ^[[27C.
# places a . character at column 27 of the current line.
# This == different from Durdraw, which would instead place
# 27 spaces, each escaped to set the color, and then a .
# character.
# fg colors
"37":1, "36":2, "35":3, "34":4, "33":5, "32":6, "31":7, "30":8,
# bg colors
"47":1, "46":2, "45":3, "44":4, "43":5, "42":6, "41":7, "40":8,
# text attributes
"0":"none", # non-bold white fg, black bg
"1":"bold", # bright color
"4":"underscore", # should we handle this?
"5":"blink", # iCE color
"7":"reverse", # should we handle this?
"8":"concealed",
}
self.escapeBgMap = {
1: "44", # blue
2: "42", # green
3: "46", # cyan
4: "41", # red
5: "45", # magenta
6: "43", # yellow/brown
7: "47", # white
8: "40", # black
0: "40", # black
}
self.escapeBgMap_old = {
1:"47", 2:"46", 3:"45", 4:"44",
5:"43", 6:"42", 7:"41", 8:"40", 0:"40"
}
def getColorCode24k(r, g, b):
""" r, g and b must be numbers between 0-255 """
code = '\033[38;2;'
code += str(r) + ';' + str(g) + ';' + str(b)
code += 'm'
return code
def getColorCodeIrc(self, fg, bg):
""" Return a string containing the IRC color code to color the next
character, for given fg/bg """
# references: https://www.mirc.com/colors.html
# http://anti.teamidiot.de/static/nei/*/extended_mirc_color_proposal.html
# map Durdraw -> IRC colors
if self.appState.colorMode == '16':
fg = color_16_to_mirc_16[fg]
bg = color_16_to_mirc_16[bg]
elif self.appState.colorMode == '256':
fg = color_256_to_mirc_16[fg]
bg = color_256_to_mirc_16[bg]
# fg = color_256_to_mirc_16[fg]
# bg = color_256_to_mirc_16[bg]
bg = 1
# build the code
code = '\x03'
#code = code + str(fg)
code = code + f'{fg:02d}'
code = code + ','
#code = code + str(bg)
code = code + f'{bg:02d}'
# code = code + '\x03'
return code
def getColorCode256(self, fg, bg):
""" Return a string containing 256-color mode ANSI escape code for
given fg/bg """
if fg <= 16 and fg > 0:
fg = color_256_to_ansi_16[fg]
code = '\033[38;5;' # begin escape sequence
code = code + str(fg)
code = code + ';'
code = code + '48;5;'
code = code + str(bg)
#code = code + str('0')
code = code + 'm'
return code
def getColorCode(self, fg, bg):
""" returns a string containing ANSI escape code for given fg/bg """
escape = '\033[' # begin escape sequence
escape = escape + self.escapeFgMap[fg] + ';' # set fg color
escape = escape + self.escapeBgMap[bg] # set bg color
escape = escape + "m" # m = set graphics mode command
return escape
def codePage437(self):
pass
def convert_colormap(self, mov, conv_table):
""" takes a dictionary that contains a coler mapper.
Modifies the movie
""" # It might be better to deepclone and return a new movie...
for frame in mov.frames:
for line in frame.newColorMap:
for pair in line:
if pair[0] in conv_table.keys():
pair[0] = conv_table[pair[0]]
if 'bg' in conv_table.keys():
if pair[1] in conv_table['bg'].keys():
pair[1] = conv_table['bg'][pair[1]]
#if pair[1] == 16:
# pair[1] = 0
#if pair[1] in conv_table.keys():
# pair[1] = conv_table[pair[1]]
def initColorPairs_general(self, fg_count=256, bg_count=16, use_order=False): # High color pairs, 256 color
""" Initialize 256 color mode color pairs """
self.colorPairMap = {}
# color order to match thedraw, etc.
color_order = [0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15, 16]
pair = 0
try:
curses.use_default_colors()
bg = 0
if use_order:
fg_range = [0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15]
else:
fg_range = list(range(0, fg_count))
for bg in range(0, bg_count):
for fg in fg_range:
curses.init_pair(pair, fg, bg)
#try:
# curses.init_extended_pair(pair, fg, bg)
#except Exception as E:
# pdb.set_trace()
self.colorPairMap.update({(fg,bg):pair})
pair += 1
self.appState.totalFgColors = fg + 1
self.appState.totalBgColors = bg + 1
self.appState.totalFgColors = fg
self.appState.totalBgColors = bg
# set pair 0 fg 0 bg to default color:
self.colorPairMap.update({(0,0):0})
return True
except Exception as E:
#debug_filename = 'debugy.txt'
#debug_file = open(debug_filename, "w")
#debug_file.write(str(E))
#debug_file.close()
return False
def initColorPairs_256color(self): # High color pairs, 256 color
return self.initColorPairs_general(fg_count=256, bg_count=1)
def initColorPairs_256color_dead_again(self): # High color pairs, 256 color
""" Initialize 256 color mode color pairs """
self.colorPairMap = {}
pair = 0
try:
curses.use_default_colors()
bg = 0
for bg in range(-1, 16):
for fg in range(0, 256):
curses.init_pair(pair, fg, bg)
self.colorPairMap.update({(fg,bg):pair})
pair += 1
self.appState.totalFgColors = fg + 1
self.appState.totalBgColors = bg + 1
self.appState.totalFgColors = fg
self.appState.totalBgColors = bg
# set pair 0 fg 0 bg to default color:
self.colorPairMap.update({(0,0):0})
return True
except Exception as E:
#debug_filename = 'debugy.txt'
#debug_file = open(debug_filename, "w")
#debug_file.write(str(self.colorPairMap))
#debug_file.close()
return False
def initColorPairs_cga(self, trans=False):
if self.appState.iceColors:
self.initColorPairs_ice_colors(trans=trans)
else:
self.initColorPairs_cga_old2(trans=trans)
#return self.initColorPairs_general(fg_count=16, bg_count=16, use_order=True)
#def initColorPairs_ice_colors(self): # 16 fg colors, 16 bg colors ice colors
def initColorPairs_ice_colors(self, trans=False): # 16 fg colors, 16 bg colors
""" Initialize 16 fg * 16 bg, or 256 color mode color pairs """
self.colorPairMap = {}
# default user-friendly color order:
# black, blue, green, cyan, red, magenta, yellow, white, 16 = nothing?
if trans:
defaultBg = -1
else:
defaultBg = curses.COLOR_BLACK
defaultBg = 0
#defaultBg = curses.COLOR_BLACK
color_order = [-1, 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15, 16]
bg_order = [0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15, 16]
try:
curses.use_default_colors()
bg = 0
map_fg = 0
map_bg = 0
#for bg in range(-1, 17):
# for fg in range(-1, 17):
#curses.init_pair(1, curses.COLOR_BLACK, bg) # black - 0
#self.colorPairMap.update({(curses.COLOR_BLACK,-1):1})
#curses.init_pair(1, curses.COLOR_BLUE, bg) # blue- 1
#self.colorPairMap.update({(curses.COLOR_BLUE,bg):2})
#curses.init_pair(2, curses.COLOR_GREEN, bg) # green - 2
#self.colorPairMap.update({(curses.COLOR_GREEN,bg):3})
#curses.init_pair(3, curses.COLOR_CYAN, bg) # cyan - 3
#self.colorPairMap.update({(curses.COLOR_CYAN,bg):4})
#curses.init_pair(4, curses.COLOR_RED, bg) # red - 4
#self.colorPairMap.update({(curses.COLOR_RED,bg):5})
#curses.init_pair(5, curses.COLOR_MAGENTA, bg) # magenta/purple - 5
#self.colorPairMap.update({(curses.COLOR_MAGENTA,bg):6})
#curses.init_pair(6, curses.COLOR_YELLOW, bg) # brown/yellow - 6
#self.colorPairMap.update({(curses.COLOR_YELLOW,bg):7})
#curses.init_pair(7, curses.COLOR_WHITE, bg) # white - 7 (and 0)
#self.colorPairMap.update({(curses.COLOR_WHITE,bg):8})
# basic ncurses colors - comments for these are durdraw internal color numbers:
curses.init_pair(0, curses.COLOR_BLACK, defaultBg) # black - 0
curses.init_pair(1, curses.COLOR_BLUE, defaultBg) # blue- 1
curses.init_pair(2, curses.COLOR_GREEN, defaultBg) # green - 2
curses.init_pair(3, curses.COLOR_CYAN, defaultBg) # cyan - 3
curses.init_pair(4, curses.COLOR_RED, defaultBg) # red - 4
curses.init_pair(5, curses.COLOR_MAGENTA, defaultBg) # magenta/purple - 5
curses.init_pair(6, curses.COLOR_YELLOW, defaultBg) # brown/yellow - 6
curses.init_pair(7, curses.COLOR_WHITE, defaultBg) # white - 7 (and 0)
curses.init_pair(8, 7, defaultBg) # white - 7 (and 0)
self.colorPairMap = {
# foreground colors, black background
(0,0):1, (1,0):1, (2,0):2, (3,0):3, (4,0):4, (5,0):5, (6,0):6, (7,0):7, (8,0):8,
}
# redo it fro scrarch:
pair = 0
#bg += 1
bg = 0
for fg in color_order:
for bg in bg_order:
curses.init_pair(pair, fg, bg)
self.colorPairMap.update({(map_fg,map_bg):pair})
pair += 1
map_fg += 1
map_bg += 1
map_fg = 0
self.appState.totalFgColors = fg + 1
self.appState.totalBgColors = bg + 1
self.appState.totalFgColors = fg
self.appState.totalBgColors = bg
# set pair 0 fg 0 bg to default color:
self.colorPairMap.update({(0,0):0})
return True
except Exception as E:
#debug_filename = 'debugy.txt'
#debug_file = open(debug_filename, "w")
#debug_file.write(str(self.colorPairMap))
#debug_file.close()
return False
def initColorPairs_256color_beta(self): # High color pairs, 256 color
""" Initialize 256 color mode color pairs """
self.colorPairMap = {}
pair = 1
try:
curses.use_default_colors()
#self.initColorPairs_cga()
#pair = 58
#for bg in range(9, 127):
# for fg in range(9, curses.COLORS):
for bg in range(0, 16):
#for fg in range(0, curses.COLORS):
for fg in range(0, 255):
#debug_write(str(pair))
curses.init_pair(pair, fg, bg)
self.colorPairMap.update({(fg,bg):pair})
pair += 1
#self.colorPairMap.update({(fg, bg):pair})
#curses.init_pair(i + 1, i, -1) # foreground only
self.appState.totalFgColors = fg
self.appState.totalBgColors = bg
return True
except Exception as E:
#debug_filename = 'debugy.txt'
#debug_file = open(debug_filename, "w")
#debug_file.write(str(self.colorPairMap))
#debug_file.close()
return False
def initColorPairs_cga_old2(self, trans=False):
""" Setup ncurses color pairs for ANSI colors """
# this kind of hurts to write. wtf, ncurses.
if trans:
defaultBg = -1
else:
defaultBg = curses.COLOR_BLACK
# basic ncurses colors - comments for these are durdraw internal color numbers:
curses.init_pair(1, curses.COLOR_BLACK, defaultBg) # black - 0
curses.init_pair(2, curses.COLOR_BLUE, defaultBg) # blue- 1
curses.init_pair(3, curses.COLOR_GREEN, defaultBg) # green - 2
curses.init_pair(4, curses.COLOR_CYAN, defaultBg) # cyan - 3
curses.init_pair(5, curses.COLOR_RED, defaultBg) # red - 4
curses.init_pair(6, curses.COLOR_MAGENTA, defaultBg) # magenta/purple - 5
curses.init_pair(7, curses.COLOR_YELLOW, defaultBg) # brown/yellow - 6
curses.init_pair(8, curses.COLOR_WHITE, defaultBg) # white - 7 (and 0)
# black with background colors
curses.init_pair(9, curses.COLOR_BLACK, curses.COLOR_BLUE) # 1,2
curses.init_pair(10, curses.COLOR_BLACK, curses.COLOR_GREEN) # 1,3
curses.init_pair(11, curses.COLOR_BLACK, curses.COLOR_CYAN) # 1,4
curses.init_pair(12, curses.COLOR_BLACK, curses.COLOR_RED) # 1,5
curses.init_pair(13, curses.COLOR_BLACK, curses.COLOR_MAGENTA) # 1,6
curses.init_pair(14, curses.COLOR_BLACK, curses.COLOR_YELLOW) # 1,7
curses.init_pair(15, curses.COLOR_BLACK, curses.COLOR_WHITE) # 1,8
# blue with background colors
curses.init_pair(16, curses.COLOR_BLUE, curses.COLOR_BLUE) # 2,2
curses.init_pair(17, curses.COLOR_BLUE, curses.COLOR_GREEN) # 2,3
curses.init_pair(18, curses.COLOR_BLUE, curses.COLOR_CYAN) # 2,4
curses.init_pair(19, curses.COLOR_BLUE, curses.COLOR_RED) # 2,5
curses.init_pair(20, curses.COLOR_BLUE, curses.COLOR_MAGENTA) # 2,6
curses.init_pair(21, curses.COLOR_BLUE, curses.COLOR_YELLOW) # 2,7
curses.init_pair(22, curses.COLOR_BLUE, curses.COLOR_WHITE) # 2,7
# green with background colors
curses.init_pair(23, curses.COLOR_GREEN, curses.COLOR_BLUE) # 3,1
curses.init_pair(24, curses.COLOR_GREEN, curses.COLOR_GREEN) # 3,2
curses.init_pair(25, curses.COLOR_GREEN, curses.COLOR_CYAN) # 3,3
curses.init_pair(26, curses.COLOR_GREEN, curses.COLOR_RED) # 3,4
curses.init_pair(27, curses.COLOR_GREEN, curses.COLOR_MAGENTA) # 3,5
curses.init_pair(28, curses.COLOR_GREEN, curses.COLOR_YELLOW) # 3,6
curses.init_pair(29, curses.COLOR_GREEN, curses.COLOR_WHITE) # 3,7
# cyan with background colors
curses.init_pair(30, curses.COLOR_CYAN, curses.COLOR_BLUE) # 4,1
curses.init_pair(31, curses.COLOR_CYAN, curses.COLOR_GREEN) # 4,2
curses.init_pair(32, curses.COLOR_CYAN, curses.COLOR_CYAN) # 4,3
curses.init_pair(33, curses.COLOR_CYAN, curses.COLOR_RED) # 4,4
curses.init_pair(34, curses.COLOR_CYAN, curses.COLOR_MAGENTA) # 4,5
curses.init_pair(35, curses.COLOR_CYAN, curses.COLOR_YELLOW) # 4,6
curses.init_pair(36, curses.COLOR_CYAN, curses.COLOR_WHITE) # 4,7
# yellow with background colors
curses.init_pair(37, curses.COLOR_RED, curses.COLOR_BLUE) # 5,1
curses.init_pair(38, curses.COLOR_RED, curses.COLOR_GREEN) # 5,2
curses.init_pair(39, curses.COLOR_RED, curses.COLOR_CYAN) # 5,3
curses.init_pair(40, curses.COLOR_RED, curses.COLOR_RED) # 5,4
curses.init_pair(41, curses.COLOR_RED, curses.COLOR_MAGENTA) # 5,5
curses.init_pair(42, curses.COLOR_RED, curses.COLOR_YELLOW) # 5,6
curses.init_pair(43, curses.COLOR_RED, curses.COLOR_WHITE) # 5,7
# green with background colors
curses.init_pair(44, curses.COLOR_MAGENTA, curses.COLOR_BLUE) # 6,1
curses.init_pair(45, curses.COLOR_MAGENTA, curses.COLOR_GREEN) # 6,2
curses.init_pair(46, curses.COLOR_MAGENTA, curses.COLOR_CYAN) # 6,3
curses.init_pair(47, curses.COLOR_MAGENTA, curses.COLOR_RED) # 6,4
curses.init_pair(48, curses.COLOR_MAGENTA, curses.COLOR_MAGENTA) # 6,5
curses.init_pair(49, curses.COLOR_MAGENTA, curses.COLOR_YELLOW) # 6,6
curses.init_pair(50, curses.COLOR_MAGENTA, curses.COLOR_WHITE) # 6,7
# red with background colors
curses.init_pair(51, curses.COLOR_YELLOW, curses.COLOR_BLUE) # 7,1
curses.init_pair(52, curses.COLOR_YELLOW, curses.COLOR_GREEN) # 7,2
curses.init_pair(53, curses.COLOR_YELLOW, curses.COLOR_CYAN) # 7,3
curses.init_pair(54, curses.COLOR_YELLOW, curses.COLOR_RED) # 7,4
curses.init_pair(55, curses.COLOR_YELLOW, curses.COLOR_MAGENTA) # 7,5
curses.init_pair(56, curses.COLOR_YELLOW, curses.COLOR_YELLOW) # 7,6
curses.init_pair(57, curses.COLOR_YELLOW, curses.COLOR_WHITE) # 7,7
# black with background colors
curses.init_pair(58, curses.COLOR_WHITE, curses.COLOR_BLUE) # 8,1
curses.init_pair(59, curses.COLOR_WHITE, curses.COLOR_GREEN) # 8,2
curses.init_pair(60, curses.COLOR_WHITE, curses.COLOR_CYAN) # 8,3
curses.init_pair(61, curses.COLOR_WHITE, curses.COLOR_RED) # 8,4
curses.init_pair(62, curses.COLOR_WHITE, curses.COLOR_MAGENTA) # 8,5
curses.init_pair(63, curses.COLOR_WHITE, curses.COLOR_YELLOW) # 8,6
curses.init_pair(64, curses.COLOR_WHITE, curses.COLOR_WHITE) # 8,7
#curses.init_pair(64, defaultBg, curses.COLOR_RED) # 8,7
# ^ this doesn't work ?!@ ncurses pair # must be between 1 and 63
# or ncurses (const?) COLOR_PAIR - 1
# fix is: have functions to swap color map from blackfg to normal.
# call that function when drawing if the fg color == black, then switch back
# after each character. Or.. keep track which map we're in in a variable.
self.colorPairMap = {
# foreground colors, black background
(0,0):1, (1,0):1, (2,0):2, (3,0):3, (4,0):4, (5,0):5, (6,0):6, (7,0):7, (8,0):8,
# and again, because black == both 0 and 8. :| let's just ditch 0?
(0,8):1, (1,8):1, (2,8):2, (3,8):3, (4,8):4, (5,8):5, (6,8):6, (7,8):7, (8,8):8,
# white with backround colors
(1,1):9, (1,2):10, (1,3):11, (1,4):12, (1,5):13, (1,6):14, (1,7):15,
# cyan with backround colors
(2,1):16, (2,2):17, (2,3):18, (2,4):19, (2,5):20, (2,6):21, (2,7):22,
# magenta with background colors
(3,1):23, (3,2):24, (3,3):25, (3,4):26, (3,5):27, (3,6):28, (3,7):29,
# blue with background colors
(4,1):30, (4,2):31, (4,3):32, (4,4):33, (4,5):34, (4,6):35, (4,7):36,
# yellow with background colors
(5,1):37, (5,2):38, (5,3):39, (5,4):40, (5,5):41, (5,6):42, (5,7):43,
# green with background colors
(6,1):44, (6,2):45, (6,3):46, (6,4):47, (6,5):48, (6,6):49, (6,7):50,
# red with background colors
(7,1):51, (7,2):52, (7,3):53, (7,4):54, (7,5):55, (7,6):56, (7,7):57,
# black with background colors
(8,1):58, (8,2):59, (8,3):60, (8,4):61, (8,5):62, (8,6):63,
#(8,7):57, # 57 instead of 64, because we switch color maps for black
# on red
(8,7):64,
# Again, this time with feeling
(0,1):58, (0,2):59, (0,3):60, (0,4):61, (0,5):62, (0,6):63,
(0,7):57,
# BRIGHT COLORS 9-16
# white with backround colors
(9,0):1, (9,8):1, (9,1):9, (9,2):10, (9,3):11, (9,4):12, (9,5):13, (9,6):14, (9,7):15,
# cyan with backround colors
(10,0):2, (10,8):2, (10,1):16, (10,2):17, (10,3):18, (10,4):19, (10,5):20, (10,6):21, (10,7):22,
# magenta with background colors
(11,0):3, (11,8):3, (11,1):23, (11,2):24, (11,3):25, (11,4):26, (11,5):27, (11,6):28, (11,7):29,
# blue with background colors
(12,0):4, (12,8):4, (12,1):30, (12,2):31, (12,3):32, (12,4):33, (12,5):34, (12,6):35, (12,7):36,
# yellow with background colors
(13,0):5, (13,8):5, (13,1):37, (13,2):38, (13,3):39, (13,4):40, (13,5):41, (13,6):42, (13,7):43,
# green with background colors
(14,0):6, (14,8):6, (14,1):44, (14,2):45, (14,3):46, (14,4):47, (14,5):48, (14,6):49, (14,7):50,
# red with background colors
(15,0):7, (15,8):7, (15,1):51, (15,2):52, (15,3):53, (15,4):54, (15,5):55, (15,6):56, (15,7):57,
# black with background colors
(16,0):8, (16,8):8, (16,1):58, (16,2):59, (16,3):60, (16,4):61, (16,5):62, (16,6):63,
#(16,7):57, # 57 instead of 64, because we switch color maps for black
(16,7):64,
} # (fg,bg):cursespair
def initColorPairs_cga_old(self):
""" Setup ncurses color pairs for ANSI colors """
# this kind of hurts to write. wtf, ncurses.
# basic ncurses colors - comments for these are durdraw internal color numbers:
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) # white - 1
curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) # cyan - 2
curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK) # magenta - 3
curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK) # blue - 4
curses.init_pair(5, curses.COLOR_YELLOW, curses.COLOR_BLACK) # yellow - 5
curses.init_pair(6, curses.COLOR_GREEN, curses.COLOR_BLACK) # green - 6
curses.init_pair(7, curses.COLOR_RED, curses.COLOR_BLACK) # red - 7
curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_BLACK) # black - 8 (and 0)
# white with background colors
curses.init_pair(9, curses.COLOR_WHITE, curses.COLOR_WHITE) # 1,1
curses.init_pair(10, curses.COLOR_WHITE, curses.COLOR_CYAN) # 1,2
curses.init_pair(11, curses.COLOR_WHITE, curses.COLOR_MAGENTA) # 1,3
curses.init_pair(12, curses.COLOR_WHITE, curses.COLOR_BLUE) # 1,4
curses.init_pair(13, curses.COLOR_WHITE, curses.COLOR_YELLOW) # 1,5
curses.init_pair(14, curses.COLOR_WHITE, curses.COLOR_GREEN) # 1,6
curses.init_pair(15, curses.COLOR_WHITE, curses.COLOR_RED) # 1,7
# cyan with background colors
curses.init_pair(16, curses.COLOR_CYAN, curses.COLOR_WHITE) # 2,1
curses.init_pair(17, curses.COLOR_CYAN, curses.COLOR_CYAN) # 2,2
curses.init_pair(18, curses.COLOR_CYAN, curses.COLOR_MAGENTA) # 2,3
curses.init_pair(19, curses.COLOR_CYAN, curses.COLOR_BLUE) # 2,4
curses.init_pair(20, curses.COLOR_CYAN, curses.COLOR_YELLOW) # 2,5
curses.init_pair(21, curses.COLOR_CYAN, curses.COLOR_GREEN) # 2,6
curses.init_pair(22, curses.COLOR_CYAN, curses.COLOR_RED) # 2,7
# magenta with background colors
curses.init_pair(23, curses.COLOR_MAGENTA, curses.COLOR_WHITE) # 3,1
curses.init_pair(24, curses.COLOR_MAGENTA, curses.COLOR_CYAN) # 3,2
curses.init_pair(25, curses.COLOR_MAGENTA, curses.COLOR_MAGENTA) # 3,3
curses.init_pair(26, curses.COLOR_MAGENTA, curses.COLOR_BLUE) # 3,4
curses.init_pair(27, curses.COLOR_MAGENTA, curses.COLOR_YELLOW) # 3,5
curses.init_pair(28, curses.COLOR_MAGENTA, curses.COLOR_GREEN) # 3,6
curses.init_pair(29, curses.COLOR_MAGENTA, curses.COLOR_RED) # 3,7
# blue with background colors
curses.init_pair(30, curses.COLOR_BLUE, curses.COLOR_WHITE) # 4,1
curses.init_pair(31, curses.COLOR_BLUE, curses.COLOR_CYAN) # 4,2
curses.init_pair(32, curses.COLOR_BLUE, curses.COLOR_MAGENTA) # 4,3
curses.init_pair(33, curses.COLOR_BLUE, curses.COLOR_BLUE) # 4,4
curses.init_pair(34, curses.COLOR_BLUE, curses.COLOR_YELLOW) # 4,5
curses.init_pair(35, curses.COLOR_BLUE, curses.COLOR_GREEN) # 4,6
curses.init_pair(36, curses.COLOR_BLUE, curses.COLOR_RED) # 4,7
# yellow with background colors
curses.init_pair(37, curses.COLOR_YELLOW, curses.COLOR_WHITE) # 5,1
curses.init_pair(38, curses.COLOR_YELLOW, curses.COLOR_CYAN) # 5,2
curses.init_pair(39, curses.COLOR_YELLOW, curses.COLOR_MAGENTA) # 5,3
curses.init_pair(40, curses.COLOR_YELLOW, curses.COLOR_BLUE) # 5,4
curses.init_pair(41, curses.COLOR_YELLOW, curses.COLOR_YELLOW) # 5,5
curses.init_pair(42, curses.COLOR_YELLOW, curses.COLOR_GREEN) # 5,6
curses.init_pair(43, curses.COLOR_YELLOW, curses.COLOR_RED) # 5,7
# green with background colors
curses.init_pair(44, curses.COLOR_GREEN, curses.COLOR_WHITE) # 6,1
curses.init_pair(45, curses.COLOR_GREEN, curses.COLOR_CYAN) # 6,2
curses.init_pair(46, curses.COLOR_GREEN, curses.COLOR_MAGENTA) # 6,3
curses.init_pair(47, curses.COLOR_GREEN, curses.COLOR_BLUE) # 6,4
curses.init_pair(48, curses.COLOR_GREEN, curses.COLOR_YELLOW) # 6,5
curses.init_pair(49, curses.COLOR_GREEN, curses.COLOR_GREEN) # 6,6
curses.init_pair(50, curses.COLOR_GREEN, curses.COLOR_RED) # 6,7
# red with background colors
curses.init_pair(51, curses.COLOR_RED, curses.COLOR_WHITE) # 7,1
curses.init_pair(52, curses.COLOR_RED, curses.COLOR_CYAN) # 7,2
curses.init_pair(53, curses.COLOR_RED, curses.COLOR_MAGENTA) # 7,3
curses.init_pair(54, curses.COLOR_RED, curses.COLOR_BLUE) # 7,4
curses.init_pair(55, curses.COLOR_RED, curses.COLOR_YELLOW) # 7,5
curses.init_pair(56, curses.COLOR_RED, curses.COLOR_GREEN) # 7,6
#curses.init_pair(57, curses.COLOR_RED, curses.COLOR_RED) # 7,7
# black with background colors
curses.init_pair(58, curses.COLOR_BLACK, curses.COLOR_WHITE) # 8,1
curses.init_pair(59, curses.COLOR_BLACK, curses.COLOR_CYAN) # 8,2
curses.init_pair(60, curses.COLOR_BLACK, curses.COLOR_MAGENTA) # 8,3
curses.init_pair(61, curses.COLOR_BLACK, curses.COLOR_BLUE) # 8,4
curses.init_pair(62, curses.COLOR_BLACK, curses.COLOR_YELLOW) # 8,5
curses.init_pair(63, curses.COLOR_BLACK, curses.COLOR_GREEN) # 8,6
curses.init_pair(57, curses.COLOR_BLACK, curses.COLOR_RED) # 8,7
#curses.init_pair(64, curses.COLOR_BLACK, curses.COLOR_RED) # 8,7
# ^ this doesn't work ?!@ ncurses pair # must be between 1 and 63
# or ncurses (const?) COLOR_PAIR - 1
# fix is: have functions to swap color map from blackfg to normal.
# call that function when drawing if the fg color == black, then switch back
# after each character. Or.. keep track which map we're in in a variable.
self.colorPairMap = {
# foreground colors, black background
(0,0):1, (1,0):1, (2,0):2, (3,0):3, (4,0):4, (5,0):5, (6,0):6, (7,0):7, (8,0):8,
# and again, because black == both 0 and 8. :| let's just ditch 0?
(0,8):1, (1,8):1, (2,8):2, (3,8):3, (4,8):4, (5,8):5, (6,8):6, (7,8):7, (8,8):8,
# white with backround colors
(1,1):9, (1,2):10, (1,3):11, (1,4):12, (1,5):13, (1,6):14, (1,7):15,
# cyan with backround colors
(2,1):16, (2,2):17, (2,3):18, (2,4):19, (2,5):20, (2,6):21, (2,7):22,
# magenta with background colors
(3,1):23, (3,2):24, (3,3):25, (3,4):26, (3,5):27, (3,6):28, (3,7):29,
# blue with background colors
(4,1):30, (4,2):31, (4,3):32, (4,4):33, (4,5):34, (4,6):35, (4,7):36,
# yellow with background colors
(5,1):37, (5,2):38, (5,3):39, (5,4):40, (5,5):41, (5,6):42, (5,7):43,
# green with background colors
(6,1):44, (6,2):45, (6,3):46, (6,4):47, (6,5):48, (6,6):49, (6,7):50,
# red with background colors
(7,1):51, (7,2):52, (7,3):53, (7,4):54, (7,5):55, (7,6):56, (7,7):57,
# black with background colors
(8,1):58, (8,2):59, (8,3):60, (8,4):61, (8,5):62, (8,6):63,
(8,7):57, # 57 instead of 64, because we switch color maps for black
# on red
# BRIGHT COLORS 9-16
# white with backround colors
(9,0):1, (9,8):1, (9,1):9, (9,2):10, (9,3):11, (9,4):12, (9,5):13, (9,6):14, (9,7):15,
# cyan with backround colors
(10,0):2, (10,8):2, (10,1):16, (10,2):17, (10,3):18, (10,4):19, (10,5):20, (10,6):21, (10,7):22,
# magenta with background colors
(11,0):3, (11,8):3, (11,1):23, (11,2):24, (11,3):25, (11,4):26, (11,5):27, (11,6):28, (11,7):29,
# blue with background colors
(12,0):4, (12,8):4, (12,1):30, (12,2):31, (12,3):32, (12,4):33, (12,5):34, (12,6):35, (12,7):36,
# yellow with background colors
(13,0):5, (13,8):5, (13,1):37, (13,2):38, (13,3):39, (13,4):40, (13,5):41, (13,6):42, (13,7):43,
# green with background colors
(14,0):6, (14,8):6, (14,1):44, (14,2):45, (14,3):46, (14,4):47, (14,5):48, (14,6):49, (14,7):50,
# red with background colors
(15,0):7, (15,8):7, (15,1):51, (15,2):52, (15,3):53, (15,4):54, (15,5):55, (15,6):56, (15,7):57,
# black with background colors
(16,0):8, (16,8):8, (16,1):58, (16,2):59, (16,3):60, (16,4):61, (16,5):62, (16,6):63,
(16,7):57, # 57 instead of 64, because we switch color maps for black
} # (fg,bg):cursespair
def initColorPairs(self):
""" Setup ncurses color pairs for ANSI colors """
# this kind of hurts to write. wtf, ncurses.
# basic ncurses colors - comments for these are durdraw internal color numbers:
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) # white - 1
curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) # cyan - 2
curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK) # magenta - 3
curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK) # blue - 4
curses.init_pair(5, curses.COLOR_YELLOW, curses.COLOR_BLACK) # yellow - 5
curses.init_pair(6, curses.COLOR_GREEN, curses.COLOR_BLACK) # green - 6
curses.init_pair(7, curses.COLOR_RED, curses.COLOR_BLACK) # red - 7
curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_BLACK) # black - 8 (and 0)
# white with background colors
curses.init_pair(9, curses.COLOR_WHITE, curses.COLOR_WHITE) # 1,1
curses.init_pair(10, curses.COLOR_WHITE, curses.COLOR_CYAN) # 1,2
curses.init_pair(11, curses.COLOR_WHITE, curses.COLOR_MAGENTA) # 1,3
curses.init_pair(12, curses.COLOR_WHITE, curses.COLOR_BLUE) # 1,4
curses.init_pair(13, curses.COLOR_WHITE, curses.COLOR_YELLOW) # 1,5
curses.init_pair(14, curses.COLOR_WHITE, curses.COLOR_GREEN) # 1,6
curses.init_pair(15, curses.COLOR_WHITE, curses.COLOR_RED) # 1,7
# cyan with background colors
curses.init_pair(16, curses.COLOR_CYAN, curses.COLOR_WHITE) # 2,1
curses.init_pair(17, curses.COLOR_CYAN, curses.COLOR_CYAN) # 2,2
curses.init_pair(18, curses.COLOR_CYAN, curses.COLOR_MAGENTA) # 2,3
curses.init_pair(19, curses.COLOR_CYAN, curses.COLOR_BLUE) # 2,4
curses.init_pair(20, curses.COLOR_CYAN, curses.COLOR_YELLOW) # 2,5
curses.init_pair(21, curses.COLOR_CYAN, curses.COLOR_GREEN) # 2,6
curses.init_pair(22, curses.COLOR_CYAN, curses.COLOR_RED) # 2,7
# magenta with background colors
curses.init_pair(23, curses.COLOR_MAGENTA, curses.COLOR_WHITE) # 3,1
curses.init_pair(24, curses.COLOR_MAGENTA, curses.COLOR_CYAN) # 3,2
curses.init_pair(25, curses.COLOR_MAGENTA, curses.COLOR_MAGENTA) # 3,3
curses.init_pair(26, curses.COLOR_MAGENTA, curses.COLOR_BLUE) # 3,4
curses.init_pair(27, curses.COLOR_MAGENTA, curses.COLOR_YELLOW) # 3,5
curses.init_pair(28, curses.COLOR_MAGENTA, curses.COLOR_GREEN) # 3,6
curses.init_pair(29, curses.COLOR_MAGENTA, curses.COLOR_RED) # 3,7
# blue with background colors
curses.init_pair(30, curses.COLOR_BLUE, curses.COLOR_WHITE) # 4,1
curses.init_pair(31, curses.COLOR_BLUE, curses.COLOR_CYAN) # 4,2
curses.init_pair(32, curses.COLOR_BLUE, curses.COLOR_MAGENTA) # 4,3
curses.init_pair(33, curses.COLOR_BLUE, curses.COLOR_BLUE) # 4,4
curses.init_pair(34, curses.COLOR_BLUE, curses.COLOR_YELLOW) # 4,5
curses.init_pair(35, curses.COLOR_BLUE, curses.COLOR_GREEN) # 4,6
curses.init_pair(36, curses.COLOR_BLUE, curses.COLOR_RED) # 4,7
# yellow with background colors
curses.init_pair(37, curses.COLOR_YELLOW, curses.COLOR_WHITE) # 5,1
curses.init_pair(38, curses.COLOR_YELLOW, curses.COLOR_CYAN) # 5,2
curses.init_pair(39, curses.COLOR_YELLOW, curses.COLOR_MAGENTA) # 5,3
curses.init_pair(40, curses.COLOR_YELLOW, curses.COLOR_BLUE) # 5,4
curses.init_pair(41, curses.COLOR_YELLOW, curses.COLOR_YELLOW) # 5,5
curses.init_pair(42, curses.COLOR_YELLOW, curses.COLOR_GREEN) # 5,6
curses.init_pair(43, curses.COLOR_YELLOW, curses.COLOR_RED) # 5,7
# green with background colors
curses.init_pair(44, curses.COLOR_GREEN, curses.COLOR_WHITE) # 6,1
curses.init_pair(45, curses.COLOR_GREEN, curses.COLOR_CYAN) # 6,2
curses.init_pair(46, curses.COLOR_GREEN, curses.COLOR_MAGENTA) # 6,3
curses.init_pair(47, curses.COLOR_GREEN, curses.COLOR_BLUE) # 6,4
curses.init_pair(48, curses.COLOR_GREEN, curses.COLOR_YELLOW) # 6,5
curses.init_pair(49, curses.COLOR_GREEN, curses.COLOR_GREEN) # 6,6
curses.init_pair(50, curses.COLOR_GREEN, curses.COLOR_RED) # 6,7
# red with background colors
curses.init_pair(51, curses.COLOR_RED, curses.COLOR_WHITE) # 7,1
curses.init_pair(52, curses.COLOR_RED, curses.COLOR_CYAN) # 7,2
curses.init_pair(53, curses.COLOR_RED, curses.COLOR_MAGENTA) # 7,3
curses.init_pair(54, curses.COLOR_RED, curses.COLOR_BLUE) # 7,4
curses.init_pair(55, curses.COLOR_RED, curses.COLOR_YELLOW) # 7,5
curses.init_pair(56, curses.COLOR_RED, curses.COLOR_GREEN) # 7,6
#curses.init_pair(57, curses.COLOR_RED, curses.COLOR_RED) # 7,7
# black with background colors
# black with background colors
curses.init_pair(58, curses.COLOR_BLACK, curses.COLOR_WHITE) # 8,1
curses.init_pair(59, curses.COLOR_BLACK, curses.COLOR_CYAN) # 8,2
curses.init_pair(60, curses.COLOR_BLACK, curses.COLOR_MAGENTA) # 8,3
curses.init_pair(61, curses.COLOR_BLACK, curses.COLOR_BLUE) # 8,4
curses.init_pair(62, curses.COLOR_BLACK, curses.COLOR_YELLOW) # 8,5
curses.init_pair(63, curses.COLOR_BLACK, curses.COLOR_GREEN) # 8,6
curses.init_pair(57, curses.COLOR_BLACK, curses.COLOR_RED) # 8,7
#curses.init_pair(64, curses.COLOR_BLACK, curses.COLOR_RED) # 8,7
# ^ this doesn't work ?!@ ncurses pair # must be between 1 and 63
# or ncurses (const?) COLOR_PAIR - 1
# fix is: have functions to swap color map from blackfg to normal.
# call that function when drawing if the fg color == black, then switch back
# after each character. Or.. keep track which map we're in in a variable.
self.colorPairMap = {
# foreground colors, black background
(0,0):1, (1,0):1, (2,0):2, (3,0):3, (4,0):4, (5,0):5, (6,0):6, (7,0):7, (8,0):8,
# and again, because black == both 0 and 8. :| let's just ditch 0?
(0,8):1, (1,8):1, (2,8):2, (3,8):3, (4,8):4, (5,8):5, (6,8):6, (7,8):7, (8,8):8,
# white with backround colors
(1,1):9, (1,2):10, (1,3):11, (1,4):12, (1,5):13, (1,6):14, (1,7):15,
# cyan with backround colors
(2,1):16, (2,2):17, (2,3):18, (2,4):19, (2,5):20, (2,6):21, (2,7):22,
# magenta with background colors
(3,1):23, (3,2):24, (3,3):25, (3,4):26, (3,5):27, (3,6):28, (3,7):29,
# blue with background colors
(4,1):30, (4,2):31, (4,3):32, (4,4):33, (4,5):34, (4,6):35, (4,7):36,
# yellow with background colors
(5,1):37, (5,2):38, (5,3):39, (5,4):40, (5,5):41, (5,6):42, (5,7):43,
# green with background colors
(6,1):44, (6,2):45, (6,3):46, (6,4):47, (6,5):48, (6,6):49, (6,7):50,
# red with background colors
(7,1):51, (7,2):52, (7,3):53, (7,4):54, (7,5):55, (7,6):56, (7,7):57,
# black with background colors
(8,1):58, (8,2):59, (8,3):60, (8,4):61, (8,5):62, (8,6):63,
(8,7):57, # 57 instead of 64, because we switch color maps for black
# on red
# BRIGHT COLORS 9-16
# white with backround colors
(9,0):1, (9,8):1, (9,1):9, (9,2):10, (9,3):11, (9,4):12, (9,5):13, (9,6):14, (9,7):15,
# cyan with backround colors
(10,0):2, (10,8):2, (10,1):16, (10,2):17, (10,3):18, (10,4):19, (10,5):20, (10,6):21, (10,7):22,
# magenta with background colors
(11,0):3, (11,8):3, (11,1):23, (11,2):24, (11,3):25, (11,4):26, (11,5):27, (11,6):28, (11,7):29,
# blue with background colors
(12,0):4, (12,8):4, (12,1):30, (12,2):31, (12,3):32, (12,4):33, (12,5):34, (12,6):35, (12,7):36,
# yellow with background colors
(13,0):5, (13,8):5, (13,1):37, (13,2):38, (13,3):39, (13,4):40, (13,5):41, (13,6):42, (13,7):43,
# green with background colors
(14,0):6, (14,8):6, (14,1):44, (14,2):45, (14,3):46, (14,4):47, (14,5):48, (14,6):49, (14,7):50,
# red with background colors
(15,0):7, (15,8):7, (15,1):51, (15,2):52, (15,3):53, (15,4):54, (15,5):55, (15,6):56, (15,7):57,
# black with background colors
(16,0):8, (16,8):8, (16,1):58, (16,2):59, (16,3):60, (16,4):61, (16,5):62, (16,6):63,
(16,7):57, # 57 instead of 64, because we switch color maps for black
} # (fg,bg):cursespair
durdraw-0.28.0/durdraw/durdraw_file.py 0000664 0000000 0000000 00000032150 14704761225 0017753 0 ustar 00root root 0000000 0000000 # Durdraw file operations - stuff related to open, save, etc
import datetime
import gzip
import os
import pdb
import pickle
import json
from durdraw.durdraw_options import Options
from durdraw.durdraw_movie import Movie
# import durdraw.durdraw_ansiparse as ansiparse
old_16_pal_to_new = {
# maps old durdraw colors, found in file version 5 and lower
# for 16 color
0: 0, # black
1: 7, # white/grey
2: 3, # cyan
3: 5, # magenta/purple
4: 1, # blue
5: 6, # brown/yellow
6: 2, # green
7: 4, # red
8: 0, # black
9: 15, # bright white
10: 11, # bright cyan
11: 13, # bright magenta
12: 9, # bright blue
13: 14, # bright yellow
14: 10, # bright green
15: 12, # bright red
16: 16, # bright black
}
old_256_pal_to_new = {
# maps old durdraw colors, found in file version 5 and lower
# for 16 color
0: 0, # black
1: 7, # white/grey
2: 3, # cyan
3: 5, # magenta/purple
4: 1, # blue
5: 6, # brown/yellow
6: 2, # green
7: 4, # red
8: 241, # bright black
9: 8, # bright red
10: 9, # bright green
11: 10, # bright yellow
12: 11, # bright blue
13: 12, # bright magenta
14: 13, # bright cyan
15: 14, # bright white
16: 15, # bright white
}
colors_to_html = {
# Maps Durdraw color numbers to HTML values
0: '#000000', # black
1: '#000000', # black
2: '#0000AA', # blue
3: '#00AA00', # green
4: '#00AAAA', # cyan
5: '#AA0000', # red
6: '#AA00AA', # magenta
7: '#AA5500', # brown/dark yellow
8: '#AAAAAA', # light gray/white
9: '#555555', # dark grey
10: '#5555FF', # bright blue
11: '#55FF55', # bright green
12: '#55FFFF', # bright cyan
13: '#FF5555', # bright red
14: '#FF55FF', # bright magenta
15: '#FFFF00', # bright yellow
16: '#FFFFFF', # bright white
}
def write_frame_to_html_file(mov, appState, frame, file_path, gzipped=False):
""" Writes a single frame to an HTML file
"""
colorMode = appState.colorMode
if gzipped:
opener = gzip.open
else:
opener = open
with opener(file_path, 'wt') as f:
movieDataHeader = '\n'
movieDataHeader += '\n'
movieDataHeader += ''
movieDataHeader += ' \n'
movieDataHeader += ' \n'
movieDataHeader += ' \n'
movieDataHeader += ' DurDraw Generated Output\n'
movieDataHeader += ' \n'
movieDataHeader += ' \n'
movieDataHeader += ' \n'
movieDataHeader += ' \n'
fileContent = movieDataHeader
#lineNum = 0
#colNum = 0
newColorMap = []
for posY in range(0, mov.sizeY):
#newColorMap.append(list())
for posX in range(0, mov.sizeX):
#newColorMap[posX].append(list(frame.colorMap[posY, posX]))
#newColorMap[posX].append(frame.newColorMap[posY][posX])
durDolor = frame.newColorMap[posY][posX]
fgColor = frame.newColorMap[posY][posX][0]
bgColor = frame.newColorMap[posY][posX][1]
c = frame.content[posY][posX]
#for line in frame.content:
# for c in line:
try:
#fgColor = frame.newColorMap[colNum][lineNum][0]
#bgColor = frame.newColorMap[colNum][lineNum][1]
if appState.colorMode == "16":
bgColor += 1
if bgColor == 9: # black duplicate
bgColor = 0
if appState.colorMode == "256":
fgColor += 1
bgColor += 1
if fgColor in colors_to_html.keys():
fgHtmlColor = colors_to_html[fgColor]
else:
fgHtmlColor = '#AAAAAA'
if bgColor in colors_to_html.keys():
bgHtmlColor = colors_to_html[bgColor]
else:
bgHtmlColor = '#000000'
#fileContent += f""
fileContent += f""
#fileContent += f""
#fileContent += f""
fileContent += str(c)
fileContent += ""
#fileContent += "
"
#colNum += 1
except Exception as e:
print(str(e))
pdb.set_trace()
fileContent += '\n'
#lineNum += 1
fileContent += '
\n'
fileContent += ' \n'
fileContent += '\n'
f.write(fileContent)
f.close()
def serialize_to_json_file(opts, appState, movie, file_path, gzipped=True):
""" Takes live Durdraw movie objects and serializes them out to a JSON files
"""
colorMode = appState.colorMode
if gzipped:
opener = gzip.open
else:
opener = open
with opener(file_path, 'wt') as f:
movieDataHeader = {
'formatVersion': opts.saveFileFormat,
'colorFormat': colorMode, # 16, 256
'preferredFont': 'fixed', # fixed, vga, amiga, etc.
'encoding': appState.charEncoding,
'name': '',
'artist': '',
'framerate': opts.framerate,
'sizeX': movie.sizeX,
'sizeY': movie.sizeY,
'extra': None,
'frames': None,
}
frameNumber = 1
fullMovie = {'DurMovie': movieDataHeader}
fullMovieFrames = []
for frame in movie.frames:
content = ''
newFrame = []
newColorMap = []
for posX in range(0, movie.sizeX):
newColorMap.append(list())
for posY in range(0, movie.sizeY):
#newColorMap[posX].append(list(frame.colorMap[posY, posX]))
try:
newColorMap[posX].append(frame.newColorMap[posY][posX])
except Exception as E:
print(E)
pdb.set_trace()
for line in frame.content:
content = ''.join(line)
newFrame.append(content)
serialized_frame = {
'frameNumber': frameNumber,
'delay': frame.delay,
'contents': newFrame,
'colorMap': newColorMap,
}
fullMovieFrames.append(serialized_frame)
frameNumber += 1
fullMovie['DurMovie']['frames'] = fullMovieFrames
fullMovieJSON = json.dumps(fullMovie, indent=2)
newMovieJSON = clean_up_json_output(fullMovieJSON)
f.write(newMovieJSON)
f.close()
def clean_up_json_output(json_str):
""" Take aggressively whitespaced nested JSON lists and remove some of
the excessive newlines and spaces. Basically format colorMap to
look nicer
"""
json_data = json_str
json_data = json_data.replace("[\n [", "[[")
json_data = json_data.replace("[\n ", "[")
json_data = json_data.replace(",\n ", ",")
json_data = json_data.replace("\n ],", "],")
json_data = json_data.replace("],\n [", "],[")
json_data = json_data.replace("\n ]", "]")
json_data = json_data.replace("\n ],", "],")
return json_data
def get_file_mod_date_time(filename):
""" Returns a string like this: 2009-10-06 10:50:01 """
t = os.path.getmtime(filename)
return str(datetime.datetime.fromtimestamp(t, tz=datetime.timezone.utc))[:19]
def get_dur_file_colorMode_and_charMode(f):
""" Returns the color mode and encoding used by the file """
f.seek(0)
try:
loadedMovieData = json.load(f)
except Exception as e:
return False
colorMode = loadedMovieData['DurMovie']['colorFormat']
try:
charEncoding = loadedMovieData['DurMovie']['encoding']
except Exception as e:
charEncoding = 'utf-8'
# convert from file format 5 to 6
#if colorMode == 'xterm-256':
# colorMode = '256'
if charEncoding == 'Utf-8':
charEncoding = 'utf-8'
return colorMode, charEncoding
def open_json_dur_file(f, appState):
""" Loads json file into opts and movie objects. Takes an open file
object. Returns the opts and mov objects, encased in a list object.
Or return False if the open fails.
"""
f.seek(0)
try:
loadedMovieData = json.load(f)
except Exception as e:
return False
width = loadedMovieData['DurMovie']['sizeX']
height = loadedMovieData['DurMovie']['sizeY']
colorMode = loadedMovieData['DurMovie']['colorFormat']
newOpts = Options(width=width, height=height)
newOpts.framerate = loadedMovieData['DurMovie']['framerate']
newOpts.saveFileFormat = loadedMovieData['DurMovie']['formatVersion']
# load frames into a new movie object
newMov = Movie(newOpts)
currentFrame = 0
lineNum = 0
for frame in loadedMovieData['DurMovie']['frames']:
#newMov.insertCloneFrame()
newMov.addEmptyFrame()
newMov.nextFrame()
for line in frame['contents']:
#pdb.set_trace()
if lineNum < len(newMov.frames[currentFrame].content):
newMov.frames[currentFrame].content[lineNum] = [c for c in line]
#newMov.frames[currentFrame].content[lineNum] = [c for c in line.encode(appState.charEncoding)]
#newMov.frames[currentFrame].content[lineNum] = [c for c in bytes(line).decode('cp437')]
#newMov.frames[currentFrame].content = line
lineNum += 1
lineNum = 0
# load color map into new movie
#pdb.set_trace()
for x in range(0, width):
for y in range(0, height):
#pdb.set_trace()
colorPair = frame['colorMap'][x][y]
if appState.colorMode == '16' and colorMode == '256':
# set a default color when down-converting color modes:
if colorPair[0] > 16:
colorPair = [8,0]
#if newOpts.saveFileFormat < 6:
# colorPair = convert_old_color_to_new(oldColorPair)
# if colorPair == None:
# pdb.set_trace()
newMov.frames[currentFrame].colorMap[y, x] = tuple(colorPair)
newMov.frames[currentFrame].newColorMap[y][x] = colorPair
# Add delay for the frame
newMov.frames[currentFrame].delay = frame['delay']
currentFrame += 1
#pdb.set_trace()
newMov.deleteCurrentFrame()
newMov.gotoFrame(1)
container = {'opts':newOpts, 'mov':newMov}
return container
def convert_old_color_to_new(oldPair, colorMode="16"):
""" takes in a frame['colorMap'][x][y] list, returns a new list with
[0] replaced by appropriately mapped # """
#newPair = oldPair
newFg = oldPair[0]
newBg = oldPair[1]
if colorMode == "16":
if oldPair[0] in old_16_pal_to_new.keys():
newFg = old_16_pal_to_new[oldPair[0]]
newFg += 1
if oldPair[1] in old_16_pal_to_new.keys():
newBg = old_16_pal_to_new[oldPair[1]]
#newBg += 1
if colorMode == "256":
if oldPair[0] in old_256_pal_to_new.keys():
newFg = old_256_pal_to_new[oldPair[0]]
newFg += 1
#if oldPair[1] in old_16_pal_to_new.keys():
# newBg = old_16_pal_to_new[oldPair[1]]
newBg = oldPair[1] # 256 does not use bg color, so don't change it
#pdb.set_trace()
return [newFg, newBg]
class DurUnpickler(pickle.Unpickler):
"""" Custom Unpickler to remove serialized module names (like __main__) from
unpickled data and replace them with "durdraw.durdraw_movie"
"""
def find_class(self, module, name):
#if module == "__main__":
module = "durdraw.durdraw_movie"
return super().find_class(module, name)
durdraw-0.28.0/durdraw/durdraw_gui_manager.py 0000664 0000000 0000000 00000001372 14704761225 0021314 0 ustar 00root root 0000000 0000000 # Does things like: Takes a mouse click command. eg: left click on X, y.
# Figures out which widget got clicked (if any), tells the widget to run
# its onClick
# Eventually this should also do things like tell the GUI handler (curses
# or kivy) to initialize
from durdraw.durdraw_gui_manager_curses import GuiHandler
class Gui():
def __init__(self, guiType=None, window=None):
self.window = window
self.handler = GuiHandler(self)
self.widgets = []
self.buttons = []
def add_button(self, widget):
self.buttons.append(widget)
def del_button(self, widget):
self.buttons.remove(widget)
def got_click(self, clickType, x, y, extra=None):
self.handler.got_click(clickType, x, y, extra=None)
durdraw-0.28.0/durdraw/durdraw_gui_manager_curses.py 0000664 0000000 0000000 00000001343 14704761225 0022676 0 ustar 00root root 0000000 0000000 # Takes clicks from gui_manager's Gui() objects and.. does stuff?
import pdb
class GuiHandler():
def __init__(self, gui):
self.gui = gui
self.window = gui.window
self.debugLine = 30
def got_click(self, clickType, y, x, extra=None):
#self.window.addstr(self.debugLine, 0, f"Clicked: {x}, {y}, {clickType}.")
# if a button was clicked, tell it:
for button in self.gui.buttons:
if x == button.realX: # if it's on the line and \/ within width area
if (y >= button.realY) and (y <= button.realY + button.width + 1):
#self.window.addstr(33 + z, 0, f"Clicky")
#pdb.set_trace()
button.on_click()
durdraw-0.28.0/durdraw/durdraw_help.py 0000664 0000000 0000000 00000000246 14704761225 0017765 0 ustar 00root root 0000000 0000000 from importlib.resources import files
def get_resource_path(module: str, name: str) -> str:
"""Load a resource file."""
return files(module).joinpath(name)
durdraw-0.28.0/durdraw/durdraw_movie.py 0000664 0000000 0000000 00000042715 14704761225 0020163 0 ustar 00root root 0000000 0000000 from copy import deepcopy
from durdraw.durdraw_options import Options
import json
import pdb
def init_list_colorMap(width, height):
""" Builds a color map consisting of a list of lists """
#return [[list([1,0]) * width] * height]
colorMap = []
dummyColor = [8, 0]
for h in range(0, height):
colorMap.append([])
for w in range(0, width):
colorMap[h].append(dummyColor)
return colorMap
def convert_dict_colorMap(oldMap, width, height):
""" Converts the old {[x,y] = (1,0)} color map
into new format: [x, y] = [1, 0] """
newMap = init_list_colorMap(width, height)
for x in range(0, height):
for y in range(0, width):
newMap[x][y] = list(oldMap[(x, y)]) # wtf was I thinking
#print(f"New color map: {newMap}") # DEBUG
return newMap
def convert_movie_16_to_256_color_palette(mov):
""" Takes movie with old 16 color palette, converts it to look
correct with 256 color palette. Returns converted movie? """
fixer = { # conversion table for the palettes
# old and bad:
# dim: 1 = white, 2 = cyan, 3 = purple, 4 = blue, 5 = brown, 6 = green, 7 = red, 8 = black
# bright: 9 = white, 10 = cyan, 11 = purple, 12 = blue, 13 = brown, 14 = green, 15 = red, 16 = bright grey
# new and good:
#
9: 15, # bright white
10: 14, # bright cyan
11: 13, # bright purple
12: 12, # bright blue,
13: 11, # bright yellow
14: 10, # bright green
15: 9, # bright red
16: 242 # bright gray
}
for frame in mov.frames:
# apply the fixer{} mapping to the frame's color map
# looks something like: frame.colorMap[posY, posX]
# returns a tuple, not a list. yeegh
pass
#for line in frame:
class Frame():
"""Frame class - single canvas size frame of animation. a traditional drawing.
"""
def __init__(self, width, height):
""" Initialize frame, content[x][y] grid """
# it's a bunch of rows of ' 'characters.
self.content = []
self.colorMap = {}
self.newColorMap = init_list_colorMap(width, height) # [[1,0], [3, 1], ...]
self.sizeX = width
self.width = width
self.sizeY = height
self.height = height
self.delay = 0 # delay == # of sec to wait at this frame.
# Generate character arrays for frame contents, fill it
# with ' ' (space) characters
for x in range(0, height):
self.content.append([])
for y in range(0, width):
self.content[x].append(' ')
self.initOldColorMap()
#self.initColorMap()
#self.newColorMap = convert_dict_colorMap(self.colorMap, width, height)
self.setDelayValue(0)
def flip_horizontal(self):
#pdb.set_trace()
self.content = self.content[::-1]
self.newColorMap.reverse()
#for x in range(0, self.height):
# #for y in range(0, self.width):
# # reverse slicing trick
# self.content[x] = self.content[x][::-1]
# #self.content[x][0] = self.content[x][0][::-1]
# self.newColorMap[x].reverse()
def flip_vertical(self):
for x in range(0, self.height):
#for y in range(0, self.width):
# reverse slicing trick
self.content[x] = self.content[x][::-1]
#self.content[x][0] = self.content[x][0][::-1]
self.newColorMap[x].reverse()
#def flip_horizontal_segment(self, startPoint, height, width, frange=None):
# """ Finish writing this, use it for the alt-k select """
# for x in range(0, self.height):
# self.content[x].reverse()
# self.newColorMap[x].reverse()
def setWidth(self, width):
self.sizeX = width
self.width = width
return true
def setHeight(self, height):
self.sizeY = height
self.height = height
return true
def setDelayValue(self, delayValue):
self.delay = delayValue
def initOldColorMap(self):
""" Builds a dictionary mapping X/Y to a FG/BG color pair """
self.colorMap = {}
for x in range(0, self.sizeY):
for y in range(0, self.sizeX):
self.colorMap.update( {(x,y):(1,0)} ) # tuple keypair (xy), tuple value (fg and bg)
def initColorMap(self, fg=7, bg=0):
""" Builds a list of lists """
return [[[fg,0] * self.sizeY] * self.sizeX]
class Movie():
""" Contains an array of Frames, options to add, remove, copy them """
def __init__(self, opts):
self.frameCount = 0 # total number of frames
self.currentFrameNumber = 0
self.sizeX = opts.sizeX # Number of columns
self.sizeY = opts.sizeY # Number of lines
self.opts = opts
self.frames = []
self.layers = {} # Key can be a layer #, or something special. eg: "masks
self.addEmptyFrame()
self.currentFrameNumber = self.frameCount
self.currentFrame = self.frames[self.currentFrameNumber - 1]
def addFrame(self, frame):
""" takes a Frame object, adds it into the movie """
self.frames.append(frame)
self.frameCount += 1
return True
def addEmptyFrame(self):
newFrame = Frame(self.sizeX, self.sizeY)
self.frames.append(newFrame)
self.frameCount += 1
return True
def insertCloneFrame(self):
""" clone current frame after current frame """
newFrame = Frame(self.sizeX, self.sizeY)
self.frames.insert(self.currentFrameNumber, newFrame)
newFrame.content = deepcopy(self.currentFrame.content)
newFrame.colorMap = deepcopy(self.currentFrame.colorMap)
newFrame.newColorMap = deepcopy(self.currentFrame.newColorMap)
self.frameCount += 1
return True
def deleteCurrentFrame(self):
if (self.frameCount == 1):
del self.frames[self.currentFrameNumber - 1]
# deleted the last frame, so make a blank one
newFrame = Frame(self.sizeX, self.sizeY)
self.frames.append(newFrame)
self.currentFrame = self.frames[self.currentFrameNumber - 1]
else:
del self.frames[self.currentFrameNumber - 1]
self.frameCount -= 1
if (self.currentFrameNumber != 1):
self.currentFrameNumber -= 1
self.currentFrame = self.frames[self.currentFrameNumber - 1]
return True
def moveFramePosition(self, startPosition, newPosition):
""" move the frame at startPosition to newPosition """
# use push and pop to remove and insert it
fromIndex = startPosition - 1
toIndex = newPosition - 1
self.frames.insert(toIndex, self.frames.pop(fromIndex))
def gotoFrame(self, frameNumber):
if frameNumber > 0 and frameNumber < self.frameCount + 1:
self.currentFrameNumber = frameNumber
self.currentFrame = self.frames[self.currentFrameNumber - 1]
return True # succeeded
else:
return False # failed - invalid input
def nextFrame(self):
if (self.currentFrameNumber == self.frameCount): # if at last frame..
self.currentFrameNumber = 1 # cycle back to the beginning
self.currentFrame = self.frames[self.currentFrameNumber - 1] # -1 bcuz frame 1 = self.frames[0]
else:
self.currentFrameNumber += 1
self.currentFrame = self.frames[self.currentFrameNumber - 1]
def prevFrame(self):
if (self.currentFrameNumber == 1):
self.currentFrameNumber = self.frameCount
self.currentFrame = self.frames[self.currentFrameNumber - 1]
else:
self.currentFrameNumber -= 1
self.currentFrame = self.frames[self.currentFrameNumber - 1]
def hasMultipleFrames(self):
if len(self.frames) > 1:
return True
else:
return False
def growCanvasWidth(self, growth):
self.sizeX += growth
self.opts.sizeX += growth
#self.width += growth
def shrinkCanvasWidth(self, shrinkage):
self.sizeY = self.sizeY - shrinkage
self.opts.sizeY = self.opts.sizeY - shrinkage
#self.width = self.width - shrinkage
def search_and_replace_color_pair(self, old_color, new_color, frange=None):
if frange != None: # apply to all frames in range
for frameNum in range(frange[0] - 1, frange[1]):
#for frame in self.frames:
frame = self.frames[frameNum]
line_num = 0
col_num = 0
for line in frame.newColorMap:
for pair in line:
if pair == old_color:
try:
frame.newColorMap[line_num][col_num] = new_color
except:
pdb.set_trace()
#found = True
col_num += 1
line_num += 1
col_num = 0
else: # only apply to current frame
frame = self.currentFrame
line_num = 0
col_num = 0
for line in frame.newColorMap:
for pair in line:
if pair == old_color:
try:
frame.newColorMap[line_num][col_num] = new_color
except:
pdb.set_trace()
#found = True
col_num += 1
line_num += 1
col_num = 0
def search_and_replace_color(self, old_color :int, new_color :int):
found = False
for frame in self.frames:
line_num = 0
for line in frame.newColorMap:
for pair in line:
if pair[0] == old_color:
frame.newColorMap[line_num][0] = new_color
found = True
if pair[1] == old_color:
frame.newColorMap[line_num][1] = new_color
found = True
line_num += 1
def search_and_replace(self, caller, search_str: str, replace_str: str):
#search_list = list(search)
found = False
frame_num = 0
line_num = 0
for frame in self.frames:
line_num = 0
for line in frame.content:
line_str = ''.join(line)
if search_str in line_str:
if len(search_str) < len(replace_str):
line_str = line_str.replace(search_str.ljust(len(replace_str)), replace_str)
else:
line_str = line_str.replace(search_str, replace_str.ljust(len(search_str)))
#caller.notify(f"found {search_str} in line:")
#caller.notify(f"{line_str}")
# inject modified line back into frame
line = list(line_str)
frame.content[line_num] = line
found = True
line_num += 1
frame_num += 1
return found
def search_for_string(self, search_str: str, caller=None):
#search_list = list(search)
found = False
frame_num = 0
line_num = 0
for frame in self.frames:
line_num = 0
for line in frame.content:
line_str = ''.join(line)
if search_str in line_str:
column_num = line_str.index(search_str) + 1
frame_num += 1
found = True
return {"line": line_num, "col": column_num, "frame": frame_num}
line_num += 1
frame_num += 1
return found # should be false if execution reaches this point
def change_palette_16_to_256(self):
# Convert from blue to bright white by reducing their value by 1
for frame in self.frames:
line_num = 0
col_num = 0
for line in frame.newColorMap:
for pair in line:
if pair[0] == 1: # black
pair[0] = 16
elif pair[0] == 16: # bright white
pair[0] = 15
elif pair[0] == 15: # bright yellow
pair[0] = 14
elif pair[0] == 14: # bright purple
pair[0] = 13
elif pair[0] == 13: # bright red
pair[0] = 12
elif pair[0] == 12: # bright cyan
pair[0] = 11
elif pair[0] == 11: # bright green
pair[0] = 10
elif pair[0] == 10: # bright blue
pair[0] = 9
elif pair[0] == 9: # bright black
pair[0] = 8
elif pair[0] == 8: # grey
pair[0] = 7
elif pair[0] == 7: # brown
pair[0] = 6
elif pair[0] == 6: # purple
pair[0] = 5
elif pair[0] == 5: # red
pair[0] = 4
elif pair[0] == 4: # cyan
pair[0] = 3
elif pair[0] == 3: # green
pair[0] = 2
elif pair[0] == 2: # blue
pair[0] = 1
col_num += 1
def change_palette_256_to_16(self):
# Convert from blue to bright white by reducing their value by 1
for frame in self.frames:
line_num = 0
col_num = 0
for line in frame.newColorMap:
for pair in line:
if pair[0] == 16: # black
pair[0] = 1
elif pair[0] == 15: # bright white
pair[0] = 16
elif pair[0] == 14: # bright yellow
pair[0] = 15
elif pair[0] == 13: # bright purple
pair[0] = 14
elif pair[0] == 12: # bright red
pair[0] = 13
elif pair[0] == 11: # bright cyan
pair[0] = 12
elif pair[0] == 10: # bright green
pair[0] = 11
elif pair[0] == 9: # bright blue
pair[0] = 10
elif pair[0] == 8: # bright black
pair[0] = 9
elif pair[0] == 7: # grey
pair[0] = 8
elif pair[0] == 6: # brown
pair[0] = 7
elif pair[0] == 5: # purple
pair[0] = 6
elif pair[0] == 4: # red
pair[0] = 5
elif pair[0] == 3: # cyan
pair[0] = 4
elif pair[0] == 2: # green
pair[0] = 3
elif pair[0] == 1: # blue
pair[0] = 2
col_num += 1
def contains_high_colors(self):
""" Returns True if any color above 16 is used, False otherwise """
for frame in self.frames:
for line in frame.newColorMap:
for pair in line:
if pair[0] > 16:
return True
return False
def contains_background_colors(self):
""" Return true if any background color is set other than black or default """
for frame in self.frames:
for line in frame.newColorMap:
for pair in line:
if pair[1] > 0:
return True
return False
def strip_backgrounds(self):
""" Change all background colors to 0, or default background """
for frame in self.frames:
for line in frame.newColorMap:
for pair in line:
pair[1] = 0
return True
def strip_unprintable_characters(self):
""" Remove all non-printable characters from canvas """
#frame_num = 0
for frame in self.frames:
line_num = 0
col_num = 0
for line in frame.content:
for char in line:
if char.isprintable():
pass
else: # Not a printable character, so replace it with a ' '
#line_str = line_str.replace(search_str, replace_str.ljust(len(search_str)))
#line = list(line_str)
frame.content[line_num][col_num] = ' '
col_num += 1
col_num = 0
line_num += 1
return True
def shift_right(self):
""" Shift all frames to the right, wrapping the last frame back to the front """
# a.insert(0,a.pop())
self.frames.insert(0, self.frames.pop())
return True
def shift_left(self):
""" Shift all frames to the left, wrapping the first frame around to the back end """
# fnord.append(fnord.pop(0))
self.frames.append(self.frames.pop(0))
return True
def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
durdraw-0.28.0/durdraw/durdraw_options.py 0000664 0000000 0000000 00000001276 14704761225 0020534 0 ustar 00root root 0000000 0000000 import json
class Options(): # config, prefs, preferences, etc. Per movie. Separate from AppState options.
""" Member variables are canvas X/Y size, Framerate, Video resolution, etc """
def __init__(self, width=80, height=23): # default options
self.framerate = 8.0
self.sizeX = width
self.sizeY = height
self.saveFileFormat = 7 # save file format version number
# version 4 is pickle, version 5 is JSON, version 6 saves color and
# character encoding formats, Version 7 uses a new palette order for 16 color
def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
durdraw-0.28.0/durdraw/durdraw_sauce.py 0000664 0000000 0000000 00000005733 14704761225 0020143 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
import struct
class SauceParser():
def __init__(self):
# Open the file, look for a sauce record,
# extract sauce data into a structure
#self.fileName = filename
# sauce data
self.title = None
self.author = None
self.group = None
self.date = None
self.year = None
self.month = None
self.day = None
self.fileType = None
self.tinfo1 = None
self.tinfo2 = None
self.width = 80
self.height = 25
# sauce data offsets
self.title_offset = 7
self.author_offset = 42
self.group_offset = 62
self.date_offset = 82
self.fileType_offset = 95
self.tinfo1_offset = 96
self.tinfo2_offset = 98
# Number of lines in the extra SAUCE comment block. 1 byte. 0 indicates no comment block is present.
self.comnt_lines = 105
self.comnt_first_line = 133 # comment lines are 64 bytes long
# other stuff
self.sauce_blob = None
self.sauce_found = False
def parse_file(self, filename):
try:
with open(filename, 'rb') as file:
file_blob = file.read()
except Exception as E:
return False
sauce_blob = file_blob[-128:]
self.sauce_blob = sauce_blob
#print(sauce_blob)
if sauce_blob[:5] == b"SAUCE":
self.sauce_found = True
self.title = struct.unpack_from('35s', sauce_blob, offset=self.title_offset)[0]
self.author = struct.unpack_from('20s', sauce_blob, offset=self.author_offset)[0]
self.group = struct.unpack_from('20s', sauce_blob, offset=self.group_offset)[0]
self.date = struct.unpack_from('8s', sauce_blob, offset=self.date_offset)[0].decode()
self.year = self.date[:4]
self.month = self.date[4:][:2]
self.day = self.date[4:][2:]
# turn bytes into nicer strings
try:
self.title = self.title.decode().rstrip(' ').strip('\x00')
except UnicodeDecodeError:
self.title = self.title.decode('cp437').rstrip(' ').strip('\x00')
try:
self.author= self.author.decode().rstrip(' ').strip('\x00')
except UnicodeDecodeError:
self.author= self.author.decode('cp437').rstrip(' ').strip('\x00')
try:
self.group= self.group.decode().rstrip(' ').strip('\x00')
except UnicodeDecodeError:
self.group= self.group.decode('cp437').rstrip(' ').strip('\x00')
self.tinfo1 = struct.unpack_from('H', sauce_blob, offset=self.tinfo1_offset)[0]
if self.tinfo1 > 1:
self.width = self.tinfo1
self.tinfo2 = struct.unpack_from('H', sauce_blob, offset=self.tinfo2_offset)[0]
if self.tinfo2 > 1:
self.height = self.tinfo2
else:
self.sauce_found = False
durdraw-0.28.0/durdraw/durdraw_ui_curses.py 0000664 0000000 0000000 00001174523 14704761225 0021051 0 ustar 00root root 0000000 0000000 # This file is where the main loop/controller lives.
# It ought to have all curses code separated out into another file.
import curses
import curses.panel
import fnmatch
import glob
import gzip
import locale
import os
import os.path
import pathlib
import pdb
import pickle
import shutil
import sys
import subprocess
import time
#from durdraw.durdraw_appstate import AppState
from durdraw.durdraw_options import Options
from durdraw.durdraw_color_curses import AnsiArtStuff
from durdraw.durdraw_movie import Movie
from durdraw.durdraw_undo import UndoManager
import durdraw.durdraw_file as durfile
from durdraw.durdraw_ui_widgets import StatusBar
import durdraw.durdraw_gui_manager as durgui
import durdraw.durdraw_movie as durmovie
import durdraw.neofetcher as neofetcher
import durdraw.durdraw_color_curses as dur_ansilib
import durdraw.durdraw_ansiparse as dur_ansiparse
import durdraw.durdraw_sauce as dursauce
import durdraw.durdraw_charsets as durchar
import durdraw.plugins.reverse_movie as reverse_plugin # transform_movie
import durdraw.plugins.repeat_movie as repeat_plugin # transform_movie
import durdraw.plugins.bounce_movie as bounce_plugin # transform_movie
class UserInterface(): # Separate view (curses) from this controller
""" Draws user interface, has main UI loop. """
#def __init__(self, stdscr, app):
def __init__(self, app):
self.opts = Options(width=app.width, height=app.height)
self.appState = app # will be filled in by main() .. run-time app state stuff
self.initCursorMode()
self.clipBoard = None # frame object
self.charMapNumber = 0
self.chMap = {}
self.chMapString = ""
self.chMap_offset = 0
self.statusBar = None
self.appState.unicodeBlockList = durchar.get_unicode_blocks_list()
self.initCharSet() # sometimes later options can store a char set to init - utf-8, cp437, etc.
os.environ.setdefault('ESCDELAY', '10')
# initialize screen and draw the 'canvas'
locale.setlocale(locale.LC_ALL, '') # set your locale
self.realstdscr = curses.initscr()
realmaxY,realmaxX = self.realstdscr.getmaxyx() # test size
self.appState.realmaxY = realmaxY
self.appState.realmaxX = realmaxX
self.statusBarLineNum = realmaxY - 2
self.stdscr = curses.newwin(realmaxY, realmaxX, 0, 0) # Curses window
if self.appState.charEncoding == 'cp437':
self.stdscr.encoding = 'cp437'
self.panel = curses.panel.new_panel(self.stdscr) # Panel for drawing, to sit below menus
self.panel.bottom()
self.panel.show()
self.pressingButton = False
self.playingHelpScreen = False
self.pushingToClip = False # true while we are holding down mouse button to draw or erase
self.metaKey = 0
self.commandMode = False
self.line_1_offset = 15
self.transportOffset = 0
#self.stdscr.box()
self.gui = durgui.Gui(guiType="curses", window=self.stdscr)
curses.start_color() # Yeayuhhh
self.ansi = AnsiArtStuff(self.appState) # obj for misc ansi-related stuff
# Disable mouse scrolling for Python versions below 3.10, as they don't have
# curses.BUTTON5_*
self.colorbg = 0 # default bg black
self.colorfg = 7 # default fg white. These are overriden by the following self.init_x_colors_misc():
if sys.version_info.major == 3:
if sys.version_info.minor < 10:
self.appState.hasMouseScroll = False
if self.appState.colorMode == "256":
if self.ansi.initColorPairs_256color():
self.init_256_colors_misc()
self.appState.totalFgColors = 255
self.appState.totalBgColors = 1
else:
self.appState.colorMode = "16"
self.appState.maxColors = 16
self.appState.totalFgColors = 16
self.appState.totalBgColors = 8
if self.appState.colorMode == "16":
self.init_16_colors_misc()
if not app.quickStart and app.showStartupScreen:
print(f"Color mode: {self.appState.colorMode}")
time.sleep(2)
try:
self.colorpair = self.ansi.colorPairMap[(self.colorfg, self.colorbg)] # set ncurss color pair
except:
self.appState.colorMode = "16"
self.appState.maxColors = 16
self.ansi.initColorPairs_cga()
self.init_16_colors_misc()
self.appState.loadThemeFromConfig("Theme-16")
if self.appState.blackbg:
self.enableTransBackground()
try:
self.colorpair = self.ansi.colorPairMap[(self.colorfg, self.colorbg)] # set ncurss color pair
except KeyError:
pdb.set_trace()
self.mov = Movie(self.opts) # initialize a new movie to work with
self.undo = UndoManager(self, appState = self.appState) # initialize undo/redo system
self.undo.setHistorySize(self.appState.undoHistorySize)
self.xy = [0, 1] # cursor position x/y - was "curs"
self.playing = False
#curses.raw()
curses.cbreak()
curses.noecho()
curses.nonl()
self.stdscr.keypad(1)
self.realmaxY,self.realmaxX = self.realstdscr.getmaxyx()
self.testWindowSize()
self.statusBar = StatusBar(self, x=self.statusBarLineNum, y=0, appState=self.appState)
if self.appState.colorMode == "16":
self.statusBar.colorPickerButton.hide()
self.statusBar.colorPicker = self.statusBar.colorPicker_16
if self.appState.playOnlyMode:
self.statusBar.hide()
else:
for button in self.statusBar.buttons:
self.gui.add_button(button)
#self.setWindowTitle("Durdraw")
# set a default drawing character
self.appState.drawChar = chr(self.chMap['f4'])
self.statusBar.drawCharPickerButton.label = self.appState.drawChar
self.statusBarLineNum = self.realmaxY - 2
def init_256_colors_misc(self):
self.appState.theme = self.appState.theme_256
self.appState.loadThemeFromConfig('Theme-256')
if self.appState.customThemeFile:
self.appState.loadThemeFile(self.appState.customThemeFile, 'Theme-256')
if self.appState.playOnlyMode == False:
self.appState.loadHelpFileThread(self.appState.durhelp256_fullpath)
#self.appState.loadHelpFile(self.appState.durhelp256_page2_fullpath, page=2)
self.colorbg = 0 # default bg black
self.colorfg = 7 # default fg white
self.appState.sideBar_minimum_width = 37
self.appState.bottomBar_minimum_height = 10
if self.statusBar != None:
self.statusBar.colorPickerButton.enabled = True
def init_16_colors_misc(self):
self.appState.colorMode = "16"
self.appState.theme = self.appState.theme_16
self.appState.loadThemeFromConfig('Theme-16')
if self.appState.playOnlyMode == False:
self.appState.loadHelpFileThread(self.appState.durhelp16_fullpath)
#self.appState.loadHelpFile(self.appState.durhelp16_page2_fullpath, page=2)
if self.appState.customThemeFile:
self.appState.loadThemeFile(self.appState.customThemeFile, 'Theme-256')
self.colorfg = 8 # default fg white
self.colorbg = 8 # default bg black
self.appState.defaultFgColor = 8
if self.appState.iceColors:
self.colorfg = 7 # default fg white
self.colorbg = 0 # default bg black
self.appState.defaultFgColor = 7
# phase 2
self.ansi.initColorPairs_cga()
#self.ansi.initColorPairs_ice_colors()
self.appState.sideBar_minimum_width = 12
self.appState.bottomBar_minimum_height = 5
if self.statusBar != None:
self.statusBar.colorPickerButton.enabled = False
def toggleMouse(self):
""" enable and disable mouse """
self.appState.hasMouse = not self.appState.hasMouse # flip true/false
if self.appState.hasMouse:
self.initMouse()
else:
self.disableMouse()
def disableMouse(self):
print('\033[?1003l') # disable mouse reporting
curses.mousemask(0)
def initMouse(self):
print('\033[?1003h') # enable mouse tracking with the XTERM API
curses.mousemask(1) # click response without drag support
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
#print('\033[?1003h') # enable mouse tracking with the XTERM API
# https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
def initCursorMode(self):
# Set cursor shape to block - this is now a command line option.
#sys.stdout.write(f"\x1b[1 q")
# 1 block blink
# 2 block no blink
# 3 underscore blink
# 4 underscore no blink
# 5 pipe blink
# 6 pipe no blink
if self.appState.screenCursorMode == "default":
return
elif self.appState.screenCursorMode == "block":
sys.stdout.write("\x1b[1 q")
elif self.appState.screenCursorMode == "underscore":
sys.stdout.write(f"\x1b[3 q")
elif self.appState.screenCursorMode == "pipe":
sys.stdout.write(f"\x1b[5 q")
sys.stdout.write("\n")
def enableMouseReporting(self):
# Use xterm API to report location of mouse cursor
print('\033[?1003h') # enable mouse tracking with the XTERM API
self.hardRefresh()
#curses.mousemask(1)
#curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
def disableMouseReporting(self):
print('\033[?1003l') # disable mouse reporting
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
self.hardRefresh()
def enableTransBackground(self):
curses.use_default_colors()
self.reloadLowColorPairs()
def reloadLowColorPairs(self):
if self.appState.colorMode == '16':
self.ansi.initColorPairs_cga(trans=True)
else:
#curses.init_pair(1, 16, -1) # black
curses.init_pair(1, 4, -1) # blue
curses.init_pair(2, 2, -1) # green
curses.init_pair(3, 6, -1) # cyan
curses.init_pair(4, 1, -1) # red
curses.init_pair(5, 5, -1) # magenta
curses.init_pair(6, 3, -1) # yellow
curses.init_pair(7, 7, -1) # white
curses.init_pair(8, 8, -1) # dark grey/bright black
curses.init_pair(9, 12, -1) # bright blue
curses.init_pair(10, 10, -1) # bright green
curses.init_pair(11, 14, -1) # bright cyan
curses.init_pair(12, 9, -1) # bright red
curses.init_pair(13, 13, -1) # bright magenta
curses.init_pair(14, 11, -1) # bright yellow
curses.init_pair(15, 15, -1) # bright white
def resetColorsToDefault(self):
#curses.use_default_colors()
self.enableTransBackground()
def enableTrueCGAColors(self):
curses.use_default_colors()
# red
intense = 1000 # hex FF
low = 0
med = self.rgb_color_to_ncurses_color(85) # hex 55
high = self.rgb_color_to_ncurses_color(170) # hex AA
# ncurses init_color takes:
# Color #, R, G, B, as 0-1000 instead of 0-255 values.
curses.init_color(1, high, low, low) # red
curses.init_color(2, low, high, low) # green
curses.init_color(3, high, med, low) # yellow/brown #AA5500
curses.init_color(4, low, low, high) # blue
curses.init_color(5, high, low, high) # magenta
curses.init_color(6, low, high, high) # cyan
curses.init_color(7, high, high, high) # white
# bright VGA colors
curses.init_color(8, med, med, med) # bright black
curses.init_color(9, intense, med, med) # red
curses.init_color(10, med, intense, med) # green
curses.init_color(11, intense, intense, low) # yelmed/brown #AA5500
curses.init_color(12, med, med, intense) # blue
curses.init_color(13, intense, med, intense) # magenta
curses.init_color(14, med, intense, intense) # cyan
curses.init_color(15, intense, intense, intense) # white
self.reloadLowColorPairs()
def enableTrueSpeccyColors(self):
curses.use_default_colors()
# red
intense = 1000 # hex FF
low = 0
med = self.rgb_color_to_ncurses_color(85) # hex 55
high = self.rgb_color_to_ncurses_color(216) # hex d8
# ncurses init_color takes:
# Color #, R, G, B, as 0-1000 instead of 0-255 values.
curses.init_color(1, high, low, low) # red
curses.init_color(2, low, high, low) # green
curses.init_color(3, high, high, low) # yellow/brown #AA5500
curses.init_color(4, low, low, high) # blue
curses.init_color(5, high, low, high) # magenta
curses.init_color(6, low, high, high) # cyan
curses.init_color(7, high, high, high) # white
# bright VGA colors
curses.init_color(8, low, low, low) # bright black
curses.init_color(9, intense, low, low) # red
curses.init_color(10, low, intense, low) # green
curses.init_color(11, intense, intense, low) # yellow/brown #AA5500
curses.init_color(12, low, low, intense) # blue
curses.init_color(13, intense, low, intense) # magenta
curses.init_color(14, low, intense, intense) # cyan
curses.init_color(15, intense, intense, intense) # white
self.reloadLowColorPairs()
def enableTrueC64Colors(self):
# Colors from https://www.c64-wiki.com/wiki/Color
curses.use_default_colors()
# red
intense = 1000 # hex FF
low = 0
med = self.rgb_color_to_ncurses_color(85) # hex 55
high = self.rgb_color_to_ncurses_color(216) # hex d8
fc = self.rgb_color_to_ncurses_color # fix color
# ncurses init_color takes:
# Color #, R, G, B, as 0-1000 instead of 0-255 values.
curses.init_color(1, fc(136), low, low) # red #880000
curses.init_color(2, low, fc(204), fc(85)) # green #00CC55
curses.init_color(3, fc(102), fc(68), 0) # brown #664400
curses.init_color(4, low, low, fc(170)) # blue #0000AA
curses.init_color(5, fc(204), fc(68), fc(204)) # violet/purple #CC44CC
curses.init_color(6, fc(170), intense, fc(238)) # cyan #AAFFEE
curses.init_color(7, fc(119), fc(119), fc(119)) # grey 2
# bright VGA colors
curses.init_color(8, fc(51), fc(51), fc(51)) # dark grey/grey 1
curses.init_color(9, intense, fc(119), fc(119)) # light red #FF7777
curses.init_color(10, fc(170), intense, fc(102)) # light green # AAFF66
curses.init_color(11, fc(238), fc(238), fc(119)) # yellow #EEEE77
curses.init_color(12, low, fc(136), intense) # light blue #0088FF
curses.init_color(13, fc(187), fc(187), fc(187)) # LIGHT GREY/grey 3! #BBBBBB
curses.init_color(14, fc(221), fc(136), fc(85)) # Orange #DD8855
curses.init_color(15, intense, intense, intense) # white #FFFFFF
self.reloadLowColorPairs()
def map_rescale_value(self, value, from_min, from_max, to_min, to_max):
""" Converts number from one scale/range to another """
# Calculate the percentage of value between from_min and from_max
percentage = (value - from_min) / (from_max - from_min)
# Map the percentage to the range between to_min and to_max
mapped_value = percentage * (to_max - to_min) + to_min
return mapped_value
def rgb_color_to_ncurses_color(self, value):
""" Takes range 0-255, converts to 0-1000 range """
ncurses_color_value = int(self.map_rescale_value(value, 0, 255, 0, 1000))
return ncurses_color_value
def setWindowTitle(self, title):
if title == "":
title = f"durdraw"
else:
title = f"durdraw - {title}"
sys.stdout.write(f"\x1b]2;{title}\x07")
sys.stdout.write(f'\33]0;{title}\a')
sys.stdout.flush()
def getPlaybackRange(self):
""" ask for playback range presses cmd-r """
self.clearStatusLine()
self.move(self.mov.sizeY, 0)
self.stdscr.nodelay(0) # wait for input when calling getch
goodResponse = False
while goodResponse == False:
self.promptPrint("Start frame for playback [currently %i of %i, 0 for All]: " % \
(self.appState.playbackRange[0], self.mov.frameCount))
curses.echo()
lowRange = self.stdscr.getstr()
if lowRange.isdigit():
lowRange = int(lowRange)
if lowRange >= 0 and lowRange <= self.mov.frameCount:
goodResponse = True
else:
self.notify("Must be a number between %i and %i" % (1, self.mov.frameCount))
goodResponse = False
else:
self.notify("Playback range not changed.")
curses.noecho()
if self.playing:
self.stdscr.nodelay(1) # don't wait for input when calling getch
return False
if lowRange == 0:
self.setPlaybackRange(1, self.mov.frameCount)
curses.noecho()
if self.playing:
self.stdscr.nodelay(1) # don't wait for input when calling getch
return True
goodResponse = False
self.clearStatusLine()
while goodResponse == False:
self.promptPrint("End frame for playback [currently %i of %i]: " % \
(self.appState.playbackRange[0], self.mov.frameCount))
curses.echo()
highRange = self.stdscr.getstr()
if highRange.isdigit():
highRange = int(highRange)
if highRange >= lowRange and highRange <= self.mov.frameCount:
goodResponse = True
else:
self.notify("Must be a number between %i and %i" % (lowRange, self.mov.frameCount))
goodResponse = False
else:
self.notify("Playback range not changed.")
curses.noecho()
if self.playing:
self.stdscr.nodelay(1) # don't wait for input when calling getch
return False
self.setPlaybackRange(lowRange, highRange)
curses.noecho()
if self.playing:
self.stdscr.nodelay(1) # don't wait for input when calling getch
return True
def setPlaybackRange(self, start, stop):
self.appState.playbackRange = (start, stop)
def gotoFrameGetInput(self):
self.clearStatusLine()
self.move(self.mov.sizeY, 0)
self.stdscr.nodelay(0) # wait for input when calling getch
goodResponse = False
while goodResponse == False:
self.promptPrint("Enter frame to go to [currently %i of %i]: " % \
(self.mov.currentFrameNumber, self.mov.frameCount))
curses.echo()
newFrameNumber = self.stdscr.getstr()
if newFrameNumber.isdigit():
newFrameNumber = int(newFrameNumber)
if newFrameNumber >= 0 and newFrameNumber <= self.mov.frameCount:
goodResponse = True
else:
self.notify("Must be a number between %i and %i" % (1, self.mov.frameCount))
goodResponse = False
else:
self.notify("Current frame number not changed.")
curses.noecho()
if self.playing:
self.stdscr.nodelay(1) # don't wait for input when calling getch
return False
if goodResponse:
self.mov.gotoFrame(newFrameNumber)
curses.noecho()
def setAppState(self, appState):
""" Takes the app state (running config options) and makes any changes
needed to the UI/etc to honor that app state """
self.appState = appState
# set undo history size
self.undo.setHistorySize(self.appState.undoHistorySize)
def setFgColor(self, fg):
self.colorfg = fg
try:
self.colorpair = self.ansi.colorPairMap[(self.colorfg, self.colorbg)]
except KeyError:
try: # If we're trying to use an unreigstered color pair, strip
# out the background and try again.
self.colorbg = 0
self.colorpair = self.ansi.colorPairMap[(self.colorfg, self.colorbg)]
except KeyError:
self.notify(f"There was an error setting the color. fg: {self.colorfg}, bg: {self.colorbg}. Please file a bug report explaining how you got to this error.")
def setBgColor(self, bg):
self.colorbg = bg
self.colorpair = self.ansi.colorPairMap[(self.colorfg, self.colorbg)]
def switchTo16ColorMode(self):
self.switchToColorMode("16")
def switchTo256ColorMode(self):
if self.appState.maxColors < 256:
self.notify("Unable to set 256 colors. Check your terminal configuration?")
else:
self.statusBar.drawCharPickerButton.show()
self.switchToColorMode("256")
def switchToColorMode(self, newMode: str):
""" newMode, eg: '16' or '256' """
self.appState.inferno = None
if newMode == "16":
#self.statusBar.colorPicker.hide()
self.appState.colorMode = "16"
self.ansi.initColorPairs_cga()
self.init_16_colors_misc()
self.mov.change_palette_256_to_16()
self.appState.loadThemeFromConfig("Theme-16")
self.statusBar.colorPickerButton.hide()
#self.statusBar.charSetButton.hide()
#if self.statusBar.colorPickerEnabled:
# self.statusBar.enableColorPicker()
if self.appState.blackbg:
self.enableTransBackground()
# switch to 16 color sidebar picker
self.statusBar.colorPicker.hide()
self.statusBar.colorPicker = self.statusBar.colorPicker_16
self.appState.totalFgColors = 16
self.appState.totalBgColors = 8
#self.statusBar.colorPicker.show()
if newMode == "256":
self.appState.colorMode = "256"
self.ansi.initColorPairs_256color()
self.init_256_colors_misc()
self.mov.change_palette_16_to_256()
self.appState.loadThemeFromConfig("Theme-256")
self.statusBar.colorPickerButton.show()
self.appState.totalFgColors = 255
self.appState.totalBgColors = 1
if self.appState.blackbg:
self.enableTransBackground()
#self.statusBar.charSetButton.show()
#if not self.statusBar.colorPickerEnabled:
# self.statusBar.disableColorPicker()
# switch to 256 color sidebar picker
self.statusBar.colorPicker.hide()
#self.statusBar.colorPicker_bg_16.hide()
self.statusBar.colorPicker = self.statusBar.colorPicker_256
#self.statusBar.colorPicker.show()
# Show color picker if needed
realmaxY,realmaxX = self.realstdscr.getmaxyx()
if self.appState.sideBarEnabled and not self.playing:
# Sidebar not showing, but enabled. Check and see if the window is wide enough
if realmaxX > self.mov.sizeX + self.appState.sideBar_minimum_width:
self.appState.sideBarShowing = True
#self.notify("Wide. Showing color picker.")
#if self.appState.colorMode == "256":
self.statusBar.colorPicker.show()
#if self.appState.colorMode == "16":
# self.statusBar.colorPicker_bg_16.show()
# Window is too narrow, but tall enough to show more stuff on the bottom.
elif realmaxY - self.appState.bottomBar_minimum_height > self.mov.sizeY:
#if self.appState.colorMode == "256":
self.statusBar.colorPicker.show()
#if self.appState.colorMode == "16":
# self.statusBar.colorPicker_bg_16.show()
def nextFgColor(self):
""" switch to next fg color, cycle back to beginning at max """
if self.appState.colorMode == "256":
if self.colorfg < 255:
newColor = self.colorfg + 1
else:
newColor = 1
elif self.appState.colorMode == "16":
if self.appState.iceColors:
hiColor = 15
else:
hiColor = 16
if self.colorfg < hiColor:
newColor = self.colorfg + 1
else:
newColor = 1
self.setFgColor(newColor)
#if self.appState.colorMode == "256":
self.statusBar.colorPicker.handler.updateFgPicker()
def prevFgColor(self):
""" switch to prev fg color, cycle around to end if at beginning """
if self.colorfg > 1:
newColor = self.colorfg - 1
else:
if self.appState.colorMode == "16":
if self.appState.iceColors:
newColor = 15
else:
newColor = 16
elif self.appState.colorMode == "256":
newColor = 255
else:
newColor = 16 # default to 16 color
self.setFgColor(newColor)
#if self.appState.colorMode == "256":
# self.statusBar.colorPicker.handler.updateFgPicker()
self.statusBar.colorPicker.handler.updateFgPicker()
def nextBgColor(self):
""" switch to the next bg color, cycle around if at beginning """
if self.appState.colorMode == "256":
lowColor = 0
#hiColor = 255
hiColor = 0
if self.colorbg < hiColor:
newColor = self.colorbg + 1
else:
newColor = lowColor
self.setBgColor(newColor)
elif self.appState.colorMode == "16":
if self.appState.iceColors:
lowColor = 0
hiColor = 15
else:
lowColor = 1
hiColor = 8
if self.colorbg < hiColor:
newColor = self.colorbg + 1
if (self.colorfg == 7 and self.colorbg == 7) or (self.colorfg == 15 and self.colorbg == 7): # skip over red on red
newColor = self.colorbg + 1
else:
newColor = lowColor
# no ice colors - only 8 bg colors
#elif self.colorbg < 8:
# newColor = self.colorbg + 1
# if (self.colorfg == 7 and self.colorbg == 7) or (self.colorfg == 15 and self.colorbg == 7): # skip over red on red
# newColor = self.colorbg + 1
#else:
# newColor = 1
self.setBgColor(newColor)
def prevBgColor(self):
""" switch to prev bg color, cycle around to end if at beginning """
if self.appState.colorMode == "256":
lowColor = 0
#hiColor = 255
hiColor = 0
if self.colorbg > lowColor:
newColor = self.colorbg - 1
else:
newColor = hiColor
self.setBgColor(newColor)
else:
if self.appState.iceColors:
lowColor = 0
hiColor = 15
else:
lowColor = 1
hiColor = 8
if self.colorbg > lowColor:
newColor = self.colorbg - 1
#if self.colorfg == 7 and self.colorbg == 7 or self.colorfg == 15 and self.colorbg == 7: # skip over red on red
# newColor = self.colorbg - 1
else:
newColor = hiColor
self.setBgColor(newColor)
def cursorOff(self):
try:
curses.curs_set(0) # turn off cursor
except curses.error:
pass # .. if terminal supports it.
def cursorOn(self):
try:
curses.curs_set(1) # turn on cursor
except curses.error:
pass # .. if terminal supports it.
def addstr(self, y, x, string, attr=None): # addstr(y, x, str[, attr]) and addstr(str[, attr])
""" Wraps ncurses addstr in a try;except, prevents addstr from
crashing cureses if it fails """
if not attr:
try:
self.stdscr.addstr(y, x, string.encode(self.appState.charEncoding, 'replace'))
#if self.appState.charEncoding == 'utf-8':
# self.stdscr.addstr(y, x, string.encode('utf-8'))
#else:
# self.stdscr.addstr(y, x, string)
except UnicodeEncodeError:
# Replace non-ascii characters with ' '
string = string.encode('ascii', 'replace').decode('ascii').replace('?', ' ')
self.stdscr.addstr(y, x, string)
except curses.error:
self.testWindowSize()
else:
try:
self.stdscr.addstr(y, x, string.encode(self.appState.charEncoding, 'replace'), attr)
#if self.appState.charEncoding == 'utf-8':
# self.stdscr.addstr(y, x, strng.encode('utf-8'), attr)
#else:
# self.stdscr.addstr(y, x, string, attr)
except UnicodeEncodeError:
string = string.encode('ascii', 'replace').decode('ascii').replace('?', ' ')
self.stdscr.addstr(y, x, string, attr)
except curses.error:
self.testWindowSize()
def move(self, y, x):
realLine = y - self.appState.topLine
realCol = x - self.appState.firstCol
try:
self.stdscr.move(realLine, realCol)
except curses.error:
self.testWindowSize()
def notify(self, message, pause=False, wait_time=2500):
self.cursorOff()
self.clearStatusLine()
self.addstr(self.statusBarLineNum, 0, message, curses.color_pair(self.appState.theme['notificationColor']))
self.stdscr.refresh()
if pause:
if self.playing:
self.stdscr.nodelay(0) # wait for input when calling getch
self.stdscr.getch()
self.stdscr.nodelay(1) # do not wait for input when calling getch
else:
self.stdscr.getch()
if not pause:
curses.napms(wait_time)
curses.flushinp()
self.clearStatusLine()
self.cursorOn()
self.stdscr.refresh()
def testWindowSize(self):
"""Test to see if window == too small for program to operate, and
go into small window mode if necessary"""
realmaxY, realmaxX = self.realstdscr.getmaxyx() # test size
if realmaxY != self.realmaxY or realmaxX != self.realmaxX:
self.resizeHandler()
self.realmaxY, self.realmaxX = realmaxY, realmaxX
#while self.realmaxX < 80 and not self.appState.playOnlyMode:
while self.realmaxX < self.appState.minWindowWidth and not self.appState.playOnlyMode:
self.smallWindowMode() # go into small window loop.stdscr
def smallWindowMode(self):
"""Clear the screen, draw a small message near 0,0 that the window
== too small. Keep doing so until the screen == resized larger."""
self.stdscr.clear()
self.stdscr.refresh()
self.realmaxY,self.realmaxX = self.realstdscr.getmaxyx()
#while self.realmaxX < self.mov.sizeX:
while self.realmaxX < self.appState.minWindowWidth:
try:
self.addstr(0, 0, "Terminal is too small for the UI.")
self.addstr(1, 0, f"Please enlarge to {self.appState.minWindowWidth} columns or larger, or press 'q' to quit")
except: # if window is too small for the message ^
pass
c = self.stdscr.getch()
if c == 113: # 113 == 'q'
self.verySafeQuit()
self.realmaxY,self.realmaxX = self.realstdscr.getmaxyx()
self.stdscr.refresh()
time.sleep(0.02)
self.stdscr.refresh()
self.stdscr.clear()
#self.testWindowSize()
def backspace(self):
if self.xy[1] > 1:
self.undo.push()
self.xy[1] = self.xy[1] - 1
if self.playing:
self.insertChar(ord(' '), fg=self.appState.defaultFgColor, bg=self.appState.defaultBgColor, frange=self.appState.playbackRange)
else:
self.insertChar(ord(' '), fg=self.appState.defaultFgColor, bg=self.appState.defaultFgColor)
self.xy[1] = self.xy[1] - 1
def deleteKeyPop(self, frange=None):
fg, bg = self.appState.defaultFgColor, self.appState.defaultBgColor
if self.xy[1] > 0:
self.undo.push()
if frange: # framge range
for frameNum in range(frange[0] - 1, frange[1]):
self.mov.frames[frameNum].content[self.xy[0]].pop(self.xy[1] - 1) # line & add a blank
self.mov.frames[frameNum].content[self.xy[0]].append(' ') # at the end of each line.
self.mov.frames[frameNum].newColorMap[self.xy[0]].pop(self.xy[1] - 1) # line & add a blank
self.mov.frames[frameNum].newColorMap[self.xy[0]].append([fg,bg]) # at the end of each line.
else:
self.mov.currentFrame.content[self.xy[0]].pop(self.xy[1] - 1)
self.mov.currentFrame.content[self.xy[0]].append(' ')
self.mov.currentFrame.newColorMap[self.xy[0]].pop(self.xy[1] - 1)
self.mov.currentFrame.newColorMap[self.xy[0]].append([fg,bg]) # at the end of each line.
def reverseDelete(self, frange=None):
fg, bg = self.appState.defaultFgColor, self.appState.defaultBgColor
if self.xy[1] > 0:
self.undo.push()
if frange: # framge range
for frameNum in range(frange[0] - 1, frange[1]):
self.mov.frames[frameNum].content[self.xy[0]].pop(self.xy[1] - 1) # line & add a blank
self.mov.frames[frameNum].content[self.xy[0]].insert(0, ' ') # at the end of each line.
self.mov.frames[frameNum].newColorMap[self.xy[0]].pop(self.xy[1] - 1) # line & add a blank
self.mov.frames[frameNum].newColorMap[self.xy[0]].insert(0, [fg,bg]) # at the end of each line.
else:
self.mov.currentFrame.content[self.xy[0]].pop(self.xy[1] - 1)
self.mov.currentFrame.content[self.xy[0]].insert(0, ' ')
self.mov.currentFrame.newColorMap[self.xy[0]].pop(self.xy[1] - 1)
self.mov.currentFrame.newColorMap[self.xy[0]].insert(0, [fg,bg]) # at the end of each line.
def insertColor(self, fg=1, bg=0, frange=None, x=None, y=None, pushUndo=True):
""" Sets the color for an x/y location on the current frame,
or a range of frames """
if pushUndo: # push onto the clipboard stack
self.undo.push()
if x == None:
x = self.xy[1]
if y == None:
y = self.xy[0]
if frange:
for fn in range(frange[0] - 1, frange[1]):
self.mov.frames[fn].newColorMap[y][x - 1] = [fg, bg]
else:
self.mov.currentFrame.newColorMap[y][x - 1] = [fg, bg]
#self.mov.currentFrame.newColorMap[self.xy[0]][self.xy[1] - 1] = [self.colorfg, self.colorbg]
def insertChar(self, c, fg=1, bg=0, frange=None, x=None, y=None, moveCursor = False, pushUndo=True):
""" insert character at current location, move cursor to the right (unless at the edge of canvas) """
if pushUndo: # push onto the clipboard stack
self.undo.push()
if x == None:
x = self.xy[1]
moveCursor = True
if y == None:
y = self.xy[0]
if frange: # frame range
for fn in range(frange[0] - 1, frange[1]):
try:
self.mov.frames[fn].content[y][x - 1] = chr(c)
#self.mov.frames[fn].colorMap.update(
# {(y,x - 1):(fg,bg)} )
self.mov.frames[fn].newColorMap[y][x - 1] = [fg, bg]
except Exception as E:
self.notify(f"There was an internal error: {E}", pause=True)
self.notify(f"Frame: {fn}, x: {x}, y: {y}, fg: {fg}, bg: {bg}")
self.notify(f"Please save your work and restart Durdraw. Sorry for the inconvenience.")
break
if x < self.mov.sizeX and moveCursor:
self.move_cursor_right()
#self.xy[1] = self.xy[1] + 1
else:
self.mov.currentFrame.content[y][x - 1] = chr(c)
#self.mov.currentFrame.colorMap.update(
# {(y,x - 1):(fg,bg)} )
self.mov.currentFrame.newColorMap[y][x - 1] = [fg, bg]
if x < self.mov.sizeX and moveCursor:
self.move_cursor_right()
#self.xy[1] = self.xy[1] + 1
def pickUpDrawingChar(self, col, line):
# Sets the drawing chaaracter to the character under teh cusror.
# esc-P
self.appState.drawChar = self.mov.currentFrame.content[line][col]
self.statusBar.drawCharPickerButton.label = self.appState.drawChar
def eyeDrop(self, col, line):
old_fg = self.colorfg
old_bg = self.colorbg
fg, bg = self.mov.currentFrame.newColorMap[line][col]
fg, bg = self.mov.currentFrame.newColorMap[line][col]
try:
self.setFgColor(fg)
self.setBgColor(bg)
except:
self.setFgColor(old_fg)
self.setBgColor(old_bg)
self.statusBar.colorPicker.handler.updateFgPicker()
def clearCanvasPrompt(self):
self.clearCanvas(prompting=True)
def clearCanvas(self, prompting = False):
clearing = True # assume we are clearing unless we prompt and say "n"
if prompting:
self.stdscr.nodelay(0) # wait for input when calling getch
self.clearStatusLine()
self.promptPrint("Are you sure you want to clear the canvas? (Y/N) " )
while prompting:
time.sleep(0.01)
c = self.stdscr.getch()
if c == 121: # 121 = y
clearing = True
prompting = False
elif c == 110: # 110 = n
clearing = False
prompting = False
self.clearStatusLine()
if clearing:
#self.undo.push() # so we can undo this operation
self.mov = Movie(self.opts) # initialize a new movie
self.setPlaybackRange(1, self.mov.frameCount)
self.undo = UndoManager(self, appState = self.appState) # reset undo system
self.appState.sauce = dursauce.SauceParser() # empty sauce
self.appState.curOpenFileName = None
self.move_cursor_topleft()
self.hardRefresh()
def searchForStringPrompt(self):
self.stdscr.nodelay(0) # wait for input when calling getch
self.promptPrint("Enter string to search: ")
curses.echo()
search_string = self.stdscr.getstr().decode('utf-8')
curses.noecho()
search_result = self.mov.search_for_string(search_string)
if search_result == False:
self.notify("No results found.")
else:
line = search_result["line"]
column = search_result["col"]
frame_num = search_result["frame"]
self.mov.gotoFrame(frame_num)
self.move_cursor_to_line_and_column(line, column)
if self.playing:
elf.stdscr.nodelay(1)
def showCharInspector(self):
line = self.xy[0]
col = self.xy[1] - 1
character = self.mov.currentFrame.content[line][col]
fg = self.mov.currentFrame.newColorMap[line][col][0]
bg = self.mov.currentFrame.newColorMap[line][col][1]
charType = self.appState.charEncoding
if charType == "utf-8":
charValue = "U+" + str(hex(ord(character)))[2:] # aye chihuahua
else:
charValue = ord(character) # ascii/cp437
inspectorString = f"Fg: {fg}, Bg: {bg}, Char: {character}, {charType} value: {charValue}"
try:
ibmpc_value = str(ord(character.encode('cp437')))
inspectorString = inspectorString + f", cp437 value: {ibmpc_value}"
except:
pass
self.notify(inspectorString, pause=True)
def clickedChMap(self, mouseX, mouseY):
# Find the character the user clicked, then set it
# This isn't very wide-character friendly yet.
char_offset = mouseX - self.chMap_offset
char_number = int(char_offset / 3.1 + 1) # lol, madness
if self.appState.debug:
self.notify(f"Clicked character area: {str([mouseX, mouseY])}, F{char_number}")
chMapKey = f"f{char_number}"
self.appState.drawChar = chr(self.chMap[chMapKey])
def clickedInfoButton(self):
realmaxY,realmaxX = self.realstdscr.getmaxyx() # test size
if realmaxX < self.mov.sizeX + self.appState.sideInfo_minimum_width: # I'm not wide enough
self.showFileInformation(notify=True)
else:
self.toggleShowFileInformation()
#self.notify(f"realmaxX: {realmaxX}, self.mov.sizeX: {self.mov.sizeX}, self.appState.sideBar_minimum_width: {self.appState.sideBar_minimum_width}", pause=True)
def toggleShowFileInformation(self):
self.appState.viewModeShowInfo = not self.appState.viewModeShowInfo
self.stdscr.clear()
def toggleDebug(self):
self.appState.debug = not self.appState.debug
def toggleWideWrapping(self):
if self.appState.wrapWidth < 120:
self.appState.wrapWidth = 120
else:
self.appState.wrapWidth = 80
self.notify(f"Line wrapping for loading ANSIs set to: {self.appState.wrapWidth}")
def toggleInjecting(self):
self.appState.can_inject = not self.appState.can_inject
def toggleIceColors(self):
self.appState.iceColors = not self.appState.iceColors
self.ansi.initColorPairs_cga()
self.init_16_colors_misc()
def showFileInformation(self, notify = False):
# eventually show a pop-up window with editable sauce info
fileName = self.appState.curOpenFileName
author = self.appState.sauce.author
title = self.appState.sauce.title
group = self.appState.sauce.group
date = self.appState.sauce.date
year = self.appState.sauce.year
month = self.appState.sauce.month
day = self.appState.sauce.day
colorMode = self.appState.colorMode
infoString = ''
infoStringList = []
#self.stdscr.nodelay(0) # wait for input when calling getch
if fileName:
infoStringList.append(f"File: {fileName}")
if self.appState.fileShortPath:
# extract folder name from full path
folderName = self.appState.fileShortPath
infoStringList.append(f"Folder: {folderName}")
if title:
infoStringList.append(f"Title: {title}")
if author:
infoStringList.append(f"Artist: {author}")
if group:
infoStringList.append(f"Group: {group}")
if date:
infoStringList.append(f"Date: {year}/{month}/{day}")
infoStringList.append(f"Width: {self.mov.sizeX}")
infoStringList.append(f"Height: {self.mov.sizeY}")
infoStringList.append(f"Color mode: {colorMode}")
if self.appState.fileColorMode:
infoStringList.append(f"File color mode: {self.appState.fileColorMode}")
infoStringList.append(f"Playing: {self.playing} ")
if len(infoStringList) > 0:
infoString = ', '.join(infoStringList)
wideViwer = False
if notify:
notifyString = f"file: {fileName}, title: {title}, author: {author}, group: {group}, width: {self.mov.sizeX}, height: {self.mov.sizeY}"
self.notify(notifyString, pause=True)
elif self.appState.viewModeShowInfo:
# check and see if the window is wide enough for a nice side sauce
wideViewer = False
realmaxY,realmaxX = self.realstdscr.getmaxyx()
if realmaxX + self.appState.firstCol >= self.mov.sizeX + self.appState.sideInfo_minimum_width:
wideViewer = True
fileInfoColumn = self.mov.sizeX + 2
#fileInfoColumn = self.mov.sizeX + 4
#fileInfoColumn = realmaxX - self.appState.sideBar_minimum_width - 1
# show me the sauce
fileInfoColor = self.appState.theme['promptColor']
if wideViewer:
# in a nice list on the right
lineNum = 3
for infoItem in infoStringList:
# truncate to prevent writing last end of line (and wrapping)
#maxLength = realmaxX - fileInfoColumn
#itemString = infoItem[maxLength:]
itemString = infoItem
self.addstr(lineNum, fileInfoColumn, itemString, fileInfoColor)
lineNum += 1
else:
self.addstr(self.realmaxY - 1, 0, infoString, curses.color_pair(fileInfoColor))
def showTransformer(self):
""" Let the user pick transformations: Bounce, Repeat, Reverse """
self.clearStatusLine()
prompting = True
while prompting:
self.promptPrint("[B]ounce, [R]epeat, or Re[v]erse?")
c = self.stdscr.getch()
if c in [98]: # b - bounce |> -> |><|
prompting = False
self.transform_bounce()
if c in [114]: # r - repeat |> -> |>|>
prompting = False
self.transform_repeat()
if c in [118]: # v - reverse |> -> <|
prompting = False
self.transform_reverse()
if c in [27]: # escape - cancel
prompting = False
def transform_bounce(self):
""" |> -> |><| Clone all the frames in the range so they repeat once, reversing the 2nd half """
self.undo.push()
self.mov = bounce_plugin.transform_movie(self.mov)
self.clearStatusLine()
self.mov.nextFrame()
self.mov.prevFrame()
self.setPlaybackRange(1, self.mov.frameCount)
#self.hardRefresh()
def transform_repeat(self):
""" |> -> |>|> Clone all the frames in the range so they repeat once """
self.undo.push()
self.mov = repeat_plugin.transform_movie(self.mov)
self.clearStatusLine()
self.mov.nextFrame()
self.mov.prevFrame()
self.setPlaybackRange(1, self.mov.frameCount)
#self.hardRefresh()
def transform_reverse(self):
""" |> -> <| """
self.undo.push()
self.mov = reverse_plugin.transform_movie(self.mov)
self.clearStatusLine()
self.mov.nextFrame()
self.mov.prevFrame()
self.hardRefresh()
def moveCurrentFrame(self):
self.undo.push()
prompting = True
self.clearStatusLine()
startPosition = self.mov.currentFrameNumber
newPosition = self.mov.currentFrameNumber
while prompting:
time.sleep(0.01)
self.promptPrint("Use left/right to move frame, press Enter when done or Esc to cancel. (%i/%i)" % (newPosition, self.mov.frameCount))
c = self.stdscr.getch()
if c in [98, curses.KEY_LEFT]:
# move frame left one position (use pop and push)
if newPosition == 1: # if at first
newPosition = self.mov.frameCount # go to end
else:
newPosition -= 1
elif c in [102, curses.KEY_RIGHT]:
if newPosition == self.mov.frameCount: # if at last
newPosition = 1 # go back to beginning
else:
newPosition += 1
elif c in [13, curses.KEY_ENTER]: # enter - save new position
self.mov.moveFramePosition(startPosition, newPosition)
self.mov.gotoFrame(newPosition)
prompting = False
elif c in [27]: # escape - cancel
self.undo.undo()
prompting = False
def increaseFPS(self):
if self.opts.framerate != 50: # max 50fps
self.opts.framerate += 1
def decreaseFPS(self):
if self.opts.framerate != 1.0: # min 1fps
self.opts.framerate -= 1
def showScrollingHelpScreen(self):
self.appState.drawBorders = False
wasPlaying = self.playing # push, to pop when we're done
oldTopLine = self.appState.topLine # dito
oldFirstCol = self.appState.firstCol # dito
self.statusBar.colorPicker.hide()
#if self.appState.colorMode == "16":
# self.statusBar.colorPicker_bg_16.hide()
self.appState.sideBarShowing = False
self.appState.topLine = 0
self.appState.firstCol = 0
self.playing = False
self.stdscr.nodelay(1) # do not wait for input when calling getch
last_time = time.time()
self.cursorOff()
self.playingHelpScreen = True
self.appState.playingHelpScreen = True
new_time = time.time()
helpMov = self.appState.helpMov
#if page == 1:
# sleep_time = (1000.0 / self.appState.helpMovOpts.framerate) / 1000.0
#elif page == 2:
# sleep_time = (1000.0 / self.appState.helpMovOpts_2.framerate) / 1000.0
sleep_time = (1000.0 / self.appState.helpMovOpts.framerate) / 1000.0
helpMov.gotoFrame(1)
self.stdscr.clear()
self.clearStatusLine()
#self.promptPrint("* Press the any key (or click) to continue *")
clickColor = self.appState.theme['clickColor']
promptColor = self.appState.theme['promptColor']
#self.addstr(self.statusBarLineNum - 1, 0, "You can use ALT or META instead of ESC. Everything this color is clickable", curses.color_pair(promptColor))
#self.addstr(self.statusBarLineNum - 1, 51, "this color", curses.color_pair(clickColor) | curses.A_BOLD)
#self.addstr(self.statusBarLineNum - 1, 65, "clickable", curses.color_pair(clickColor) | curses.A_BOLD)
helpMov.search_and_replace(self, '{ver}', f"{self.appState.durVer}")
helpMov.search_and_replace(self, '{colormode}', f"{self.appState.colorMode}")
helpMov.search_and_replace(self, "{charmode}", f"{self.appState.charEncoding}")
helpMov.search_and_replace(self, "{pyver}", f"{self.appState.pyVersion}")
while self.playingHelpScreen:
self.move(self.xy[0], self.xy[1])
self.refresh()
#self.addstr(self.statusBarLineNum + 1, 0, "Up/Down, Pgup/Pgdown, Home/End or Mouse Wheel to scroll. Enter or Esc to exit.", curses.color_pair(promptColor)) <- now done in self.refresh()
#self.addstr(self.statusBarLineNum - 1, 51, "this color", curses.color_pair(clickColor) | curses.A_BOLD)
mouseState = False
c = self.stdscr.getch()
if c == curses.KEY_MOUSE: # get some mouse input if available
try:
_, mouseX, mouseY, _, mouseState = curses.getmouse()
except:
pass
if c in [curses.KEY_UP, ord('k')]: # scroll up
if self.appState.topLine > 0:
self.appState.topLine = self.appState.topLine - 1
elif c in [curses.KEY_DOWN, ord('j')]: # scroll down
if self.appState.topLine + self.realmaxY < helpMov.sizeY: # wtf?
self.appState.topLine += 1
elif c in [339, curses.KEY_PPAGE, ord('u'), ord('b'), ord('<')]: # page up, and vim keys
self.appState.topLine = self.appState.topLine - self.realmaxY + 3
if self.appState.topLine < 0:
self.appState.topLine = 0
elif c in [338, curses.KEY_NPAGE, ord(' '), ord('d'), ord('f'), ord('>')]: # page down, and vi keys
self.appState.topLine += self.realmaxY - 3 # go down 25 lines or whatever
if self.appState.topLine > helpMov.sizeY - self.realmaxY:
self.appState.topLine = helpMov.sizeY - self.realmaxY
elif c in [339, curses.KEY_HOME]: # 339 = home
self.appState.topLine = 0
elif c in [338, curses.KEY_END]: # 338 = end
self.appState.topLine = helpMov.sizeY - self.realmaxY + 2
#elif c != -1: # -1 means no keys are pressed.
#elif c == curses.KEY_ENTER:
elif c in [10, 13, curses.KEY_ENTER, 27, ord('q')]: # 27 == escape key
self.playingHelpScreen = False
else:
if not self.appState.hasMouseScroll:
curses.BUTTON5_PRESSED = 0
curses.BUTTON4_PRESSED = 0
try:
curses.BUTTON5_PRESSED
except:
curses.BUTTON5_PRESSED = 0
try:
curses.BUTTON4_PRESSED
except:
curses.BUTTON4_PRESSED = 0
if mouseState & curses.BUTTON4_PRESSED: # wheel up
if self.appState.topLine > 0:
self.appState.topLine = self.appState.topLine - 1
elif mouseState & curses.BUTTON5_PRESSED: # wheel down
if self.appState.topLine + self.realmaxY < helpMov.sizeY: # wtf?
self.appState.topLine += 1
new_time = time.time()
frame_delay = helpMov.currentFrame.delay
if frame_delay > 0:
realDelayTime = frame_delay
else:
realDelayTime = sleep_time
if new_time >= (last_time + realDelayTime): # Time to update the frame? If so...
last_time = new_time
# draw animation
if helpMov.currentFrameNumber == helpMov.frameCount:
helpMov.gotoFrame(1)
else:
helpMov.nextFrame()
else:
time.sleep(0.008) # to keep from sucking up cpu
if not wasPlaying:
self.stdscr.nodelay(0) # back to wait for input when calling getch
self.cursorOn()
self.appState.playingHelpScreen = False
self.appState.playingHelpScreen_2 = False
self.playingHelpScreen = False
self.stdscr.clear()
# pop old state
self.playing = wasPlaying
self.appState.topLine = oldTopLine
self.appState.firstCol = oldFirstCol
self.appState.drawBorders = True
def showAnimatedHelpScreen(self, page=1):
self.appState.drawBorders = False
wasPlaying = self.playing # push, to pop when we're done
oldTopLine = self.appState.topLine # dito
oldFirstCol = self.appState.firstCol # dito
self.appState.topLine = 0
self.appState.firstCol = 0
self.playing = False
self.stdscr.nodelay(1) # do not wait for input when calling getch
last_time = time.time()
self.cursorOff()
self.playingHelpScreen = True
self.appState.playingHelpScreen = True
if page == 2:
self.appState.playingHelpScreen_2 = True
else:
self.appState.playingHelpScreen_2 = False
new_time = time.time()
if page == 2:
helpMov = self.appState.helpMov_2
else:
helpMov = self.appState.helpMov
if page == 1:
sleep_time = (1000.0 / self.appState.helpMovOpts.framerate) / 1000.0
elif page == 2:
sleep_time = (1000.0 / self.appState.helpMovOpts_2.framerate) / 1000.0
helpMov.gotoFrame(1)
self.stdscr.clear()
self.clearStatusLine()
self.promptPrint("* Press the any key (or click) to continue *")
clickColor = self.appState.theme['clickColor']
promptColor = self.appState.theme['promptColor']
self.addstr(self.statusBarLineNum - 1, 0, "You can use ALT or META instead of ESC. Everything this color is clickable", curses.color_pair(promptColor))
self.addstr(self.statusBarLineNum - 1, 51, "this color", curses.color_pair(clickColor) | curses.A_BOLD)
self.addstr(self.statusBarLineNum - 1, 65, "clickable", curses.color_pair(clickColor) | curses.A_BOLD)
while self.playingHelpScreen:
self.move(self.xy[0], self.xy[1])
self.refresh()
if page == 2:
self.addstr(12, 51, f"{self.appState.durVer}", curses.color_pair(promptColor))
self.addstr(13, 54, f"{self.appState.colorMode}", curses.color_pair(promptColor))
self.addstr(14, 62, f"{self.appState.charEncoding}", curses.color_pair(promptColor))
c = self.stdscr.getch()
if c != -1: # -1 means no keys are pressed.
self.playingHelpScreen = False
new_time = time.time()
frame_delay = helpMov.currentFrame.delay
if frame_delay > 0:
realDelayTime = frame_delay
else:
realDelayTime = sleep_time
if new_time >= (last_time + realDelayTime): # Time to update the frame? If so...
last_time = new_time
# draw animation
if helpMov.currentFrameNumber == helpMov.frameCount:
helpMov.gotoFrame(1)
else:
helpMov.nextFrame()
else:
time.sleep(0.008) # to keep from sucking up cpu
if not wasPlaying:
self.stdscr.nodelay(0) # back to wait for input when calling getch
self.cursorOn()
self.appState.playingHelpScreen = False
self.appState.playingHelpScreen_2 = False
self.playingHelpScreen = False
self.stdscr.clear()
self.playing = wasPlaying
self.appState.topLine = oldTopLine
self.appState.firstCol = oldFirstCol
self.appState.drawBorders = True
def showViewerHelp(self):
""" Show the help screen for the player/viewer mode """
helpString = "Up/down Pgup/Pgdown Home/end - Scroll, i - File Info. -/+ - Speed. q - Exit Viewer"
self.notify(helpString, pause=True)
self.cursorOff()
def apply_neofetch_keys(self):
""" Called by the user at Runtime to search/replace inside the app. """
neofetch = self.appState.isAppAvail("neofetch")
if neofetch:
self.undo.push()
self.appState.fetchData = neofetcher.run() # populate fetchData with NeoFetch data ...
self.replace_neofetch_keys() # ... so this can work.
self.refresh()
else:
self.notify("Neofetch not found in path. Please install it and try again.")
return False
def replace_neofetch_keys(self):
""" Find all the Neofetch {keys} in the drawing, and replace them with
Neofetch populated data """
for key in neofetcher.neo_keys:
dur_key = '{' + key + '}'
self.mov.search_and_replace(self, dur_key, self.appState.fetchData[key])
def startPlaying(self, mov=None, opts=None):
""" Start playing the animation - start a "game" style loop, make FPS
by drawing if current time == greater than a delta plus the time
the last frame was drawn.
"""
tempMovie = None
tempOpts = None
if mov != None: # playing a movie other than the main or help file
tempMovie = self.mov
self.mov = mov
if opts != None:
tempOpts = self.opts
self.opts = opts
self.commandMode = False
oldTopLine = self.appState.topLine
oldFirstCol = self.appState.firstCol
cursorMode = self.appState.cursorMode
if not self.statusBar.toolButton.hidden:
self.statusBar.toolButton.draw()
self.statusBar.toolButton.hide()
if not self.statusBar.animButton.hidden:
self.statusBar.animButton.draw()
self.statusBar.animButton.hide()
self.drawStatusBar()
self.stdscr.nodelay(1) # do not wait for input when calling getch
last_time = time.time()
#self.statusBar.drawCharPickerButton.hide()
if self.appState.playOnlyMode:
self.statusBar.colorPicker.hide()
#self.statusBar.colorPicker_bg_16.hide()
self.appState.sideBarShowing = False
self.statusBar.hide()
self.cursorOff()
self.setWindowTitle(self.appState.curOpenFileName)
self.playing = True
self.metaKey = 0
if self.appState.playOnlyMode:
self.appState.drawBorders = False
if not self.appState.playOnlyMode:
# mode, show extra stuff.
self.drawStatusBar()
playedTimes = 1
new_time = time.time()
# see how many milliseconds we have to sleep for
# then divide by 1000.0 since time.sleep() uses seconds
sleep_time = (1000.0 / self.opts.framerate) / 1000.0
self.mov.gotoFrame(self.appState.playbackRange[0])
mouseX, mouseY = 0, 0
while self.playing:
# catch keyboard input - to change framerate or stop playing animation
# get keyboard input, returns -1 if none available
self.move(self.xy[0], self.xy[1])
# Here refreshScreen=False because we will self.stdscr.refresh() below, after drawing the status bar (to avoid flicker)
self.refresh(refreshScreen=False)
if self.appState.viewModeShowInfo:
self.showFileInformation()
if not self.appState.playOnlyMode:
self.drawStatusBar()
self.move(self.xy[0], self.xy[1] - 1) # reposition cursor
c = self.stdscr.getch()
# handle resize
resized = False
realmaxY,realmaxX = self.realstdscr.getmaxyx()
if self.appState.realmaxY != realmaxY:
resized = True
self.appState.realmaxY = realmaxY
if self.appState.realmaxX != realmaxX:
resized = True
self.appState.realmaxX = realmaxX
if resized:
pass
#self.notify("Debug: resized")
#self.appState.topLine = 0
#self.appState.firstCol = 0
#debugstring = f"self.appState.realmaxY: {self.appState.realmaxY}, self.appState.realmaxX: {self.appState.realmaxX}, topLine: {self.appState.topLine}, firstCol: {self.appState.firstCol}"
#self.addstr(self.statusBarLineNum - 1, 0, debugstring)
self.stdscr.refresh()
if c == 27:
self.metaKey = 1
self.pressingButton = False
if self.pushingToClip:
self.pushingToClip = False
self.disableMouseReporting()
self.commandMode = True
c = self.stdscr.getch() # normal esc
# Clear out any canvas state as needed for command mode. For example...
# If we think the mouse button is pressed.. stop thinking that.
# In other words, un-stick the mouse button in case it's stuck:
if self.metaKey == 1 and not self.appState.playOnlyMode and c != curses.ERR: # esc
self.pressingButton = False
#if cursorMode != "Draw" and cursorMode != "Paint":
# print('\033[?1003l') # disable mouse reporting
# self.hardRefresh()
# curses.mousemask(1)
# curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if self.pushingToClip:
self.pushingToClip = False
if c == 91: c = self.stdscr.getch() # alt-arrow does this in this mrxvt 5.x build
if c in [61, 43]: # esc-= and esc-+ - fps up
self.increaseFPS()
sleep_time = (1000.0 / self.opts.framerate) / 1000.0
elif c in [45]: # esc-- (alt minus) - fps down
self.decreaseFPS()
sleep_time = (1000.0 / self.opts.framerate) / 1000.0
elif c in [98, curses.KEY_LEFT]: # alt-left - prev bg color (in 16)
if self.appState.colorMode == "16":
self.prevBgColor()
elif self.appState.colorMode == "256":
self.prevFgColor()
#self.statusBar.colorPicker.handler.move_down_256()
c = None
elif c in [102, curses.KEY_RIGHT]: # alt-right - next bg color
if self.appState.colorMode == "16":
self.nextBgColor()
elif self.appState.colorMode == "256":
self.nextFgColor()
#self.statusBar.colorPicker.handler.move_down_256()
c = None
elif c in [curses.KEY_DOWN, "\x1b\x1b\x5b\x42"]: # alt-down - prev fg color
if self.appState.colorMode == "16":
self.prevFgColor()
elif self.appState.colorMode == "256":
self.statusBar.colorPicker.handler.move_down_256()
c = None
elif c == curses.KEY_UP: # alt-up - next fg color
if self.appState.colorMode == "16":
self.nextFgColor()
elif self.appState.colorMode == "256":
self.statusBar.colorPicker.handler.move_up_256()
c = None
elif c == 91 or c == 339: # alt-[ previous character set. apparently this doesn't work
self.prevCharSet() # during playback, c == -1, so 339 is alt-pgup, as a backup
elif c == 93 or c == 338: # alt-] (93) or aplt-pagdown next character set
self.nextCharSet()
elif c == 83: # alt-S - pick a character set
self.showCharSetPicker()
self.stdscr.nodelay(1) # do not wait for input when calling getch
elif c == 46: # alt-. - insert column
self.addCol(frange=self.appState.playbackRange)
elif c == 44: # alt-, - erase/pop current column
self.delCol(frange=self.appState.playbackRange)
elif c == 47: # alt-/ - insert line
self.addLine(frange=self.appState.playbackRange)
elif c == 39: # alt-' - erase line
self.delLine(frange=self.appState.playbackRange)
elif c == 105: # alt-i - File/Canvas Information
self.clickedInfoButton()
elif c == 109 or c == 102: # alt-m or alt-f - load menu
#self.statusBar.menuButton.on_click()
self.commandMode = False
self.openMenu("File")
elif c == 99: # alt-c - color picker
self.commandMode = False
#if self.appState.colorMode == "256":
# self.statusBar.colorPickerButton.on_click()
#self.statusBar.colorPickerButton.on_click()
self.selectColorPicker()
elif c == ord(' '): # alt-space - insert drawing character
drawChar = self.appState.drawChar
x_param = self.xy[1]
y_param = self.xy[0]
self.insertChar(ord(drawChar), fg=self.colorfg, bg=self.colorbg, x=x_param, y=y_param, moveCursor=True, pushUndo=True, frange=self.appState.playbackRange)
elif c == 122: # alt-z = undo
self.clickedUndo()
elif c == 114: # alt-r = redo
self.clickedRedo()
elif c == 82: # alt-R = set playback range
self.getPlaybackRange()
elif c in [112]: # alt-p - stop playing
self.stopPlaying()
elif c == 111: # alt-o - open
self.stopPlaying()
self.openFromMenu() # as if we clicked menu->open
elif c in [104, 63]: # alt-h - help
self.showHelp()
c = None
elif c == 113: # alt-q - quit
self.safeQuit()
self.stdscr.nodelay(1)
c = None
elif c in [ord('1')]: # esc-1 copy of F1 - insert extended character
self.insertChar(self.chMap['f1'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
self.hardRefresh()
elif c in [ord('2')]: # esc-2 copy of F2 - insert extended character
self.insertChar(self.chMap['f2'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
self.hardRefresh()
elif c in [ord('3')]: # F3 - insert extended character
self.insertChar(self.chMap['f3'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
self.hardRefresh()
elif c in [ord('4')]: # F4 - insert extended character
self.insertChar(self.chMap['f4'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
self.hardRefresh()
elif c in [ord('5')]: # F5 - insert extended character
self.insertChar(self.chMap['f5'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
self.hardRefresh()
elif c in [ord('6')]: # F6 - insert extended character
self.insertChar(self.chMap['f6'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
self.hardRefresh()
elif c in [ord('7')]: # F7 - insert extended character
self.insertChar(self.chMap['f7'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
self.hardRefresh()
elif c in [ord('8')]: # F8 - insert extended character
self.insertChar(self.chMap['f8'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
self.hardRefresh()
elif c in [ord('9')]: # F9 - insert extended character
self.insertChar(self.chMap['f9'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
self.hardRefresh()
elif c in [ord('0')]: # F10 - insert extended character
self.insertChar(self.chMap['f10'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
self.hardRefresh()
else:
if self.appState.debug:
self.notify("keystroke: %d" % c) # alt-unknown
self.commandMode = 0
self.metaKey = 0
c = None
elif c != -1: # -1 means no keys are pressed.
# up or down to change framerate, otherwise stop playing
if self.appState.playOnlyMode: # UI for Play-only mode
mouseState = False
if c == curses.KEY_MOUSE: # to support mouse wheel scrolling
try:
_, mouseX, mouseY, _, mouseState = curses.getmouse()
except:
pass
realmaxY,realmaxX = self.realstdscr.getmaxyx()
if mouseState == curses.BUTTON1_CLICKED:
pass
#self.showFileInformation()
elif mouseState == curses.BUTTON1_DOUBLE_CLICKED:
# It's as if we'd pressed enter to exit the viewer mode.
self.playing = False
self.appState.topLine = 0
if not self.appState.hasMouseScroll:
curses.BUTTON5_PRESSED = 0
curses.BUTTON4_PRESSED = 0
try:
curses.BUTTON5_PRESSED
except:
curses.BUTTON5_PRESSED = 0
try:
curses.BUTTON4_PRESSED
except:
curses.BUTTON4_PRESSED = 0
if mouseState & curses.BUTTON4_PRESSED: # wheel up
if self.appState.topLine > 0:
self.appState.topLine = self.appState.topLine - 1
elif mouseState & curses.BUTTON5_PRESSED: # wheel down
if self.appState.topLine + self.realmaxY < self.mov.sizeY: # wtf?
self.appState.topLine += 1
elif c in [339, curses.KEY_PPAGE, ord('u'), ord('b')]: # page up, and vim keys
self.appState.topLine = self.appState.topLine - self.realmaxY + 3
if self.appState.topLine < 0:
self.appState.topLine = 0
elif c in [338, curses.KEY_NPAGE, ord(' '), ord('d'), ord('f')]: # page down, and vi keys
if self.mov.sizeY > self.realmaxY - 3: # if the ansi is larger than a page...
self.appState.topLine += self.realmaxY - 3 # go down 25 lines or whatever
if self.appState.topLine > self.mov.sizeY - self.realmaxY:
self.appState.topLine = self.mov.sizeY - self.realmaxY
# prevent a ghost image on any blank lines at the bottom:
#self.stdscr.clear()
#self.refresh()
elif c in [339, curses.KEY_HOME]: # 339 = home
self.appState.topLine = 0
elif c in [338, curses.KEY_END]: # 338 = end
self.appState.topLine = self.mov.sizeY - self.realmaxY + 2
elif c == curses.KEY_LEFT: # left - scroll left
self.scroll_viewer_left()
elif c == curses.KEY_RIGHT: # right - scroll right
self.scroll_viewer_right()
if c in [61, 43]: # esc-= and esc-+ - fps up
self.increaseFPS()
sleep_time = (1000.0 / self.opts.framerate) / 1000.0
elif c in [45]: # esc-- (alt minus) - fps down
self.decreaseFPS()
sleep_time = (1000.0 / self.opts.framerate) / 1000.0
if c in [ord('q'), ord('Q')]:
self.playing = False
self.appState.topLine = 0
if not self.appState.editorRunning:
self.verySafeQuit()
elif c in [10, 13, curses.KEY_ENTER, 27]: # 27 = esc
self.playing = False
self.appState.topLine = 0
elif c in [ord('?'), ord('h')]:
self.showViewerHelp()
elif c in [ord('i'), ord('I')]:
# toggle showing info. True/false swap:
self.appState.viewModeShowInfo = not self.appState.viewModeShowInfo
self.stdscr.clear()
self.refresh()
#self.showFileInformation()
elif c in [curses.KEY_DOWN, ord('j')]:
if self.appState.topLine + self.realmaxY < self.mov.sizeY: # wtf?
self.appState.topLine += 1
elif c in [curses.KEY_UP, ord('k')]:
if self.appState.topLine > 0:
self.appState.topLine = self.appState.topLine - 1
elif c == 12: # ctrl-l - harder refresh
self.stdscr.clear()
self.hardRefresh()
c = None
else:
if c == curses.KEY_MOUSE: # Remember, we are playing here
try:
_, mouseX, mouseY, _, mouseState = curses.getmouse()
except:
pass
realmaxY,realmaxX = self.realstdscr.getmaxyx()
# enable mouse tracking only when the button is pressed
if mouseState == curses.BUTTON1_CLICKED:
if self.pressingButton:
self.pressingButton = False
if cursorMode != "Draw" and cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if self.pushingToClip:
self.pushingToClip = False
if not self.appState.hasMouseScroll:
curses.BUTTON5_PRESSED = 0
curses.BUTTON4_PRESSED = 0
try:
curses.BUTTON5_PRESSED
except:
curses.BUTTON5_PRESSED = 0
try:
curses.BUTTON4_PRESSED
except:
curses.BUTTON4_PRESSED = 0
if mouseState & curses.BUTTON4_PRESSED: # wheel up
self.move_cursor_up()
elif mouseState & curses.BUTTON5_PRESSED: # wheel down
self.move_cursor_down()
if mouseState & curses.BUTTON1_PRESSED:
#if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX: # in edit area
if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX \
and mouseY + self.appState.topLine < self.appState.topLine + self.statusBarLineNum:
if not self.pressingButton:
self.pressingButton = True
print('\033[?1003h') # enable mouse tracking with the XTERM APIP
self.hardRefresh()
else:
self.pressingButton = False
if cursorMode != "Draw" and cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if self.pushingToClip:
self.pushingToClip = False
else:
if self.pressingButton:
self.pressingButton = False
if self.pushingToClip:
self.pushingToClip = False
#if self.appState.cursorMode != "Draw":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if self.pressingButton or mouseState == curses.BUTTON1_CLICKED: # self.playing == True
self.gui.got_click("Click", mouseX, mouseY)
if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX \
and mouseY + self.appState.topLine < self.appState.topLine + self.statusBarLineNum:
# we clicked in edit area, so move the cursor
self.xy[1] = mouseX + 1 # set cursor position
self.xy[0] = mouseY + self.appState.topLine
elif mouseX < realmaxX and mouseY in [self.statusBarLineNum, self.statusBarLineNum+1]: # we clicked on the status bar while playing.
if mouseY == self.statusBarLineNum: # clicked upper bar
offset = 6 # making room for the menu bar
tOffset = realmaxX - (realmaxX - self.transportOffset) + 6
if not self.appState.narrowWindow:
if mouseX in [tOffset, tOffset + 1]: # clicked pause button
self.clickHighlight(tOffset, "||")
self.stopPlaying()
elif mouseX == 12 + offset: # clicked FPS down
self.clickHighlight(12 + offset, "<")
self.decreaseFPS()
sleep_time = (1000.0 / self.opts.framerate) / 1000.0
elif mouseX == 16 + offset: # clicked FPS up
self.clickHighlight(16 + offset, ">")
self.increaseFPS()
sleep_time = (1000.0 / self.opts.framerate) / 1000.0
elif mouseY == self.statusBarLineNum+1: # clicked bottom bar
if not self.appState.narrowWindow:
if mouseX in range(4,20):
fg = mouseX - 3
self.setFgColor(fg)
elif mouseX in range(25,33):
bg = mouseX - 24
self.setBgColor(bg)
elif mouseX == self.chMap_offset + len(self.chMapString): # clicked next character set
self.clickHighlight(self.chMap_offset + len(self.chMapString), ">", bar='bottom')
self.nextCharSet()
elif mouseX == self.chMap_offset - 1: # clicked previous character set
self.clickHighlight(self.chMap_offset - 1, "<", bar='bottom')
self.prevCharSet()
if self.appState.debug:
self.notify("bottom bar. " + str([mouseX, mouseY]))
elif self.appState.debug:
self.notify("clicked. " + str([mouseX, mouseY]))
elif c == curses.KEY_LEFT: # left - move cursor right a character
self.move_cursor_left()
elif c == curses.KEY_RIGHT: # right - move cursor right
self.move_cursor_right()
elif c == curses.KEY_UP: # up - move cursor up
self.move_cursor_up()
elif c == curses.KEY_DOWN: # down - move curosr down
self.move_cursor_down()
elif c in [339, curses.KEY_PPAGE]: # page up
self.move_cursor_pgup()
elif c in [338, curses.KEY_NPAGE]: # page down
self.move_cursor_pgdown()
elif c in [339, curses.KEY_HOME]: # 339 = home
self.xy[1] = 1
elif c in [338, curses.KEY_END]: # 338 = end
self.xy[1] = self.mov.sizeX
elif c in [10, 13, curses.KEY_ENTER]: # enter (10 if we
self.move_cursor_enter()
# don't do curses.nonl())
elif c in [263, 127]: # backspace
self.backspace()
elif c in [9, 353]: # 9 = tab, 353 = shift-tab
#if self.appState.colorMode == "256":
# self.statusBar.colorPickerButton.on_click()
self.statusBar.colorPickerButton.on_click()
elif c in [330]: # delete
self.deleteKeyPop(frange=self.appState.playbackRange)
elif c in [383]: # shift-delete - delete from opposite direction
self.reverseDelete(frange=self.appState.playbackRange)
elif c in [1, curses.KEY_HOME]: # ctrl-a or home
self.xy[1] = 1
elif c in [5, curses.KEY_END]: # ctrl-e or end
self.xy[1] = self.mov.sizeX
elif c in [curses.KEY_F1]: # F1 - insert extended character
self.insertChar(self.chMap['f1'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
elif c in [curses.KEY_F2]: # F2 - insert extended character
self.insertChar(self.chMap['f2'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
elif c in [curses.KEY_F3]: # F3 - insert extended character
self.insertChar(self.chMap['f3'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
elif c in [curses.KEY_F4]: # F4 - insert extended character
self.insertChar(self.chMap['f4'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
elif c in [curses.KEY_F5]: # F5 - insert extended character
self.insertChar(self.chMap['f5'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
elif c in [curses.KEY_F6]: # F6 - insert extended character
self.insertChar(self.chMap['f6'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
elif c in [curses.KEY_F7]: # F7 - insert extended character
self.insertChar(self.chMap['f7'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
elif c in [curses.KEY_F8]: # F8 - insert extended character
self.insertChar(self.chMap['f8'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
elif c in [curses.KEY_F9]: # F9 - insert extended character
self.insertChar(self.chMap['f9'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
elif c in [curses.KEY_F10]: # F10 - insert extended character
self.insertChar(self.chMap['f10'], fg=self.colorfg, bg=self.colorbg,
frange=self.appState.playbackRange)
c = None
elif c != None and c <= 128 and c >= 32: # normal printable character
self.insertChar(c, fg=self.colorfg, bg=self.colorbg, frange=self.appState.playbackRange)
new_time = time.time()
frame_delay = self.mov.currentFrame.delay
if frame_delay > 0:
realDelayTime = frame_delay
else:
realDelayTime = sleep_time # sleep_time == (1000.0 / self.opts.framerate) / 1000.0
if new_time >= (last_time + realDelayTime): # Time to update the frame? If so...
last_time = new_time
# draw animation
if not self.appState.playOnlyMode:
if self.mov.currentFrameNumber == self.appState.playbackRange[1]:
self.mov.gotoFrame(self.appState.playbackRange[0])
else:
self.mov.nextFrame()
else:
self.mov.nextFrame()
if not self.appState.playOnlyMode: # if we're not in play-only
# mode, show extra stuff.
self.drawStatusBar()
else:
if self.appState.playNumberOfTimes > 0: # if we're playing x times
if self.mov.currentFrameNumber == self.mov.frameCount:
# and on the last frame
if playedTimes < self.appState.playNumberOfTimes:
playedTimes += 1
else: # we've played the desired number of times.
self.playing = False
#self.refresh()
else: time.sleep(0.005) # to keep from sucking up cpu
if tempMovie != None: # Need to switch back to main movie
self.mov = tempMovie
if tempOpts != None:
self.opts = tempOpts
self.appState.topLine = oldTopLine
self.appState.firstCol = oldFirstCol
self.stdscr.nodelay(0) # back to wait for input when calling getch
self.cursorOn()
def stopPlaying(self):
self.playing = False
self.statusBar.toolButton.show()
self.statusBar.animButton.show()
if self.appState.cursorMode == "Draw" or self.appState.cursorMode == "Paint":
self.statusBar.drawCharPickerButton.show()
self.enableMouseReporting()
def genCharSet(self, firstChar): # firstChar is a unicode number
newSet = {}
newChar = firstChar
for x in range(1,11): # 1-10
keyStr = 'f%i' % x
newSet.update({keyStr:newChar})
newChar += 1
return newSet
def initCharSet(self): # we can have nextCharSet and PrevCharSet to switch between chars in set
# Can also add encoding= paramater for different encodings, eg: ascii, utf-8, etc.
self.charMapNumber = 0
if self.appState.characterSet == "Durdraw Default":
self.fullCharMap = [ \
# All of our unicode templates live here. Blank template:
#{'f1':, 'f2':, 'f3':, 'f4':, 'f5':, 'f6':, 'f7':, 'f8':, 'f9':, 'f10':},
# block characters
{'f1':9617, 'f2':9618, 'f3':9619, 'f4':9608, 'f5':9600, 'f6':9604, 'f7':9612, 'f8':9616, 'f9':9632, 'f10':183 }, # ibm-pc looking block characters (but unicode instead of ascii)
{'f1':9601, 'f2':9602, 'f3':9603, 'f4':9604, 'f5':9605, 'f6':9606, 'f7': 9607, 'f8':9608, 'f9' :9600, 'f10':0x2594 }, # more block elements - mostly bottom half fills
{'f1':0x2588, 'f2':0x2589, 'f3':0x258A, 'f4':0x258B, 'f5':0x258C, 'f6':0x258D, 'f7':0x258E, 'f8':0x258F, 'f9':0x2590, 'f10':0x2595}, # partial left and right fills
# geometric shapes
#{'f1':0x25dc, 'f2':0x25dd, 'f3':0x25de, 'f4':0x25df, 'f5':0x25e0, 'f6':0x25e1, 'f7':0x25e2, 'f8':0x25e3, 'f9':0x25e4, 'f10':0x25e5}, # little curves and triangles
{'f1':0x25e2, 'f2':0x25e3, 'f3':0x25e5, 'f4':0x25e4, 'f5':0x25c4, 'f6':0x25ba, 'f7':0x25b2, 'f8':0x25bc, 'f9':0x25c0, 'f10':0x25b6 }, # little curves and triangles
#{'f1':'🮜', 'f2':'🮝', 'f3':'🮞', 'f4':'🮟', 'f5':0x25c4, 'f6':0x25ba, 'f7':0x25b2, 'f8':0x25bc, 'f9':0x25c0, 'f10':0x25b6 }, # little curves and triangles
# terminal graphic characters
{'f1':9622, 'f2':9623, 'f3':9624, 'f4':9625, 'f5':9626, 'f6':9627, 'f7':9628, 'f8':9629, 'f9':9630, 'f10':9631 }, # terminal graphic characters
#{'f1':0x1FB9C, 'f2':0x1FB9D, 'f3':0x1FB9F, 'f4':0x1FB9E, 'f5':0x1FB9A, 'f6':0x1FB9B, 'f7':0x1FB65, 'f8':0x1FB5A, 'f9':0x1FB4B, 'f10':0x1FB40}, # legacy computing smooth terminal mosaic characters - newer versions of unicode Triangles and shit.. not sure why it isn't working
#{'f1':9581, 'f2':9582, 'f3':9583, 'f4':9584, 'f5':9585, 'f6':9586, 'f7':9587, 'f8':9472, 'f9':9474, 'f10':9532 }, # character cell arcs, aka curved pipes
{'f1':9581, 'f2':9582, 'f3':9584, 'f4':9583, 'f5':9472, 'f6':9474, 'f7':9585, 'f8':9586, 'f9':9587, 'f10':9532 }, # character cell arcs, aka curved pipes
#{'f1':130032, 'f2':0x1FBF2, 'f3':0x1FBF3, 'f4':0x1FBF4, 'f5':0x1FBF5, 'f6':0x1FBF6, 'f7':0x1FBF7, 'f8':0x1FBF8, 'f9':0x1FBF9, 'f10':0x1FBF0}, # lcd/led-style digits
#{'f1':0x1FB8C, 'f2':ord('🭀'), 'f3':0x1FBF3, 'f4':0x1FBF4, 'f5':0x1FBF5, 'f6':0x1FBF6, 'f7':0x1FBF7, 'f8':0x1FBF8, 'f9':0x1FBF9, 'f10':0x1FBF0}, # lcd/led-style digits
]
# generate some unicode sets via offset
self.fullCharMap.append(self.genCharSet(0x25a0)) # geometric shapes
self.fullCharMap.append(self.genCharSet(0x25e6)) # more geometric shapes
self.fullCharMap.append(self.genCharSet(0x25c6)) # geometrics - diamond and circles
self.fullCharMap.append(self.genCharSet(0x02ef)) # UPA modifiers
self.fullCharMap.append(self.genCharSet(0x02c2)) # UPA modifiers
self.fullCharMap.append(self.genCharSet(0x2669)) # music symbols
self.fullCharMap.append(self.genCharSet(0xFF66)) # half-width kanji letters
self.fullCharMap.append(self.genCharSet(0xFF70)) # half-width kanji letters
self.fullCharMap.append(self.genCharSet(0xFF7a)) # half-width kanji letters
self.fullCharMap.append(self.genCharSet(0xFF84)) # half-width kanji letters
self.fullCharMap.append(self.genCharSet(0xFF8e)) # half-width kanji letters
#self.fullCharMap.append(self.genCharSet(0xFF98)) # half-width kanji letters
#self.fullCharMap.append(self.genCharSet(0x1F603)) # smiley emojis
self.fullCharMap.append(self.genCharSet(0x2801)) # braile a-j
self.fullCharMap.append(self.genCharSet(0x2805)) # braile k-t
self.fullCharMap.append(self.genCharSet(0x2825)) # braile u+
self.fullCharMap.append(self.genCharSet(0x2b2c)) # ellipses
self.chMap = self.fullCharMap[self.charMapNumber]
# Map a dict of F1-f10 to character values
#if self.appState.charEncoding == 'cp437':
# # ibm-pc/cp437 ansi block character
# self.chMap = {'f1':176, 'f2':177, 'f3':178, 'f4':219, 'f5':223, 'f6':220, 'f7':221, 'f8':222, 'f9':254, 'f10':250 }
# self.fullCharMap = [ self.chMap ]
# self.appState.colorPickChar = self.appState.CP438_BLOCK # ibm-pc/cp437 ansi block character
# self.appState.blockChar = self.appState.CP438_BLOCK
# self.appState.drawChar = self.appState.CP438_BLOCK
elif self.appState.characterSet == "Unicode Block":
self.setUnicodeBlock(block=self.appState.unicodeBlock)
self.chMap = self.fullCharMap[self.charMapNumber]
self.appState.colorPickChar = self.appState.UTF8_BLOCK # ibm-pc/cp437 ansi block character
self.appState.blockChar = self.appState.UTF8_BLOCK
self.appState.drawChar = self.appState.UTF8_BLOCK
self.refreshCharMap()
def setCharacterSet(self, set_name):
""" Set a Durdraw character set (not a Unicode block name) """
self.appState.characterSet = set_name
miniSetName = f"{self.appState.characterSet[:3]}.."
if self.appState.showCharSetButton:
self.statusBar.charSetButton.label = miniSetName # [Name..]
def setUnicodeBlock(self, block="Symbols for Legacy Computing"):
self.fullCharMap = durchar.load_unicode_block(block)
self.chMap = self.fullCharMap[self.charMapNumber]
if self.statusBar:
if self.appState.characterSet == "Unicode Block":
miniSetName = f"{self.appState.unicodeBlock[:3]}.."
else:
miniSetName = f"{self.appState.characterSet[:3]}.."
if self.appState.showCharSetButton:
self.statusBar.charSetButton.label = miniSetName # [Name..]
self.refreshCharMap()
#self.chMapString = "F1%cF2%cF3%cF4%cF5%cF6%cF7%cF8%cF9%cF10%c" % \
#self.chMapString = "F1%c F2%c F3%c F4%c F5%c F6%c F7%c F8%c F9%c F10%c " % \
# (self.chMap['f1'], self.chMap['f2'], self.chMap['f3'], self.chMap['f4'], self.chMap['f5'], \
# self.chMap['f6'], self.chMap['f7'], self.chMap['f8'], self.chMap['f9'], self.chMap['f10'] )
def nextCharSet(self):
if self.charMapNumber == len(self.fullCharMap) - 1:
self.charMapNumber = 0
else:
self.charMapNumber += 1
self.refreshCharMap()
def prevCharSet(self):
if self.charMapNumber == 0:
self.charMapNumber = len(self.fullCharMap) - 1
else:
self.charMapNumber -= 1
self.refreshCharMap()
def refreshCharMap(self):
# checks self.charMapNumber and does the rest
self.chMap = self.fullCharMap[self.charMapNumber]
#self.chMapString = "F1%c F2%c F3%c F4%c F5%c F6%c F7%c F8%c F9%c F10%c " % \
# Build a string, one character a time
self.chMapString = ""
for fkey in range(1,11):
keyIndex = f"f{fkey}"
keyString = f"F{fkey}"
try: # See if we can encode character in the current mode.
#keyChar = chr(self.chMap[keyIndex]).encode(self.appState.charEncoding)
keyChar = chr(self.chMap[keyIndex]).encode(self.appState.charEncoding)
#keyChar = keyChar.encode(self.appState.charEncoding)
except: # If character can't encode, eg: in cp437 mode, make it blank
keyChar = ' '
self.chMapString += f"{keyString}{keyChar}"
self.chMapString = "F1%cF2%cF3%cF4%cF5%cF6%cF7%cF8%cF9%cF10%c" % \
(self.chMap['f1'], self.chMap['f2'], self.chMap['f3'], self.chMap['f4'], self.chMap['f5'], \
self.chMap['f6'], self.chMap['f7'], self.chMap['f8'], self.chMap['f9'], self.chMap['f10'] )
#self.chMapString = self.chMapStringncoding=self.appState.charEncoding)
def clearStatusLine(self):
# fyi .. width and height in this context should go into
# appState, not in Movie(). In other words, this == not
# the width and height of the movie, but of the editor screen.
realmaxY,realmaxX = self.realstdscr.getmaxyx()
self.addstr(self.statusBarLineNum, 0, " " * realmaxX)
self.addstr(self.statusBarLineNum+1, 0, " " * realmaxX)
def resizeHandler(self):
""" Called when the window is resized """
# stick bottom of drawing to the bottom of the window
# when resizing
topLine = self.mov.sizeY - self.realmaxY - 2
if topLine < 0:
topLine = 0
#self.appState.topLine = topLine
if self.xy[0] < self.appState.topLine: # if cursor is off screen
self.xy[0] = self.appState.topLine # put it back on
def drawStatusBar(self):
if self.statusBar.hidden:
return False
if self.realmaxX >= self.appState.full_ui_width: # Wide big window
self.appState.narrowWindow = False
# show menu buttons
self.statusBar.menuButton.show()
self.statusBar.toolButton.show()
self.statusBar.animButton.show()
self.statusBar.drawCharPickerButton.show()
else: # narrow small window
self.appState.narrowWindow = True
# hide menu buttons
self.statusBar.menuButton.hide()
self.statusBar.toolButton.hide()
self.statusBar.animButton.hide()
self.statusBar.drawCharPickerButton.hide()
self.setWindowTitle(self.appState.curOpenFileName)
mainColor = self.appState.theme['mainColor']
clickColor = self.appState.theme['clickColor']
# This has also become a bit of a window resize handler
if not self.playing:
self.clearStatusLine()
else:
#self.clearStatusBarNoRefresh()
pass
realmaxY,realmaxX = self.realstdscr.getmaxyx()
resized = False
if self.appState.realmaxY != realmaxY:
resized = True
if self.appState.realmaxX != realmaxX:
#self.notify("resized")
resized = True
if resized:
self.stdscr.clear()
self.appState.realmaxY = realmaxY
self.appState.realmaxX = realmaxX
# How far right to put the toolbar's little animation
# stuff. Frame, FPS, Delay and Range.
# Move it far right enough for the menus.
# self.line_1_offset = 15
if self.appState.narrowWindow:
self.line_1_offset = -2 # anchor left
else:
self.line_1_offset = realmaxX - 63 # anchor to the right by transport
line_1_offset = self.line_1_offset
statusBarLineNum = realmaxY - 2
self.appState.sideBarShowing = False
# If the window is wide enough for the "side bar" (where sticky color goes)
if self.playing and self.appState.sideBarShowing:
self.appState.sideBarShowing = False
self.statusBar.colorPicker.hide()
#if self.appState.colorMode == "16":
# self.statusBar.colorPicker_bg_16.hide()
if not self.appState.sideBarShowing and self.appState.sideBarEnabled and not self.playing:
# Sidebar not showing, but enabled. Check and see if the window is wide enough
if realmaxX > self.mov.sizeX + self.appState.sideBar_minimum_width:
self.appState.sideBarShowing = True
#self.notify("Wide. Showing color picker.")
#if self.appState.colorMode == "256":
# self.statusBar.colorPicker.show()
self.statusBar.colorPicker.show()
#if self.appState.colorMode == "16":
# self.statusBar.colorPicker_bg_16.show()
#if not self.playing and self.appState.colorMode == "256":
if not self.playing:
if self.window_big_enough_for_colors():
self.appState.sideBarShowing = True
self.statusBar.colorPicker.show()
#if self.appState.colorMode == "16":
# self.statusBar.colorPicker_bg_16.show()
else:
self.statusBar.colorPicker.hide()
#if self.appState.colorMode == "16":
# self.statusBar.colorPicker_bg_16.hide()
if self.playing:
self.statusBar.colorPicker.hide()
#if self.appState.colorMode == "16":
# self.statusBar.colorPicker_bg_16.hide()
self.appState.sideBarColumn = realmaxX - self.appState.sideBar_minimum_width - 1
#if self.appState.sideBarShowing:
# We are clear to draw the Sidebar
# Anchor the color picker to the bottom right
#new_colorPicker_y = realmaxY - self.appState.colorBar_height - 2
#if self.appState.colorMode == "16":
# # Make some space for colorPicker to be above the BG picker.
# bg_height = self.statusBar.colorPicker_bg_16.handler.height + 1
#else:
# bg_height = 0
#new_colorPicker_y = realmaxY - self.statusBar.colorPicker.handler.height - bg_height - 2
new_colorPicker_y = realmaxY - self.statusBar.colorPicker.handler.height - 2
new_colorPicker_x = realmaxX - self.statusBar.colorPicker.handler.width
self.statusBar.colorPicker.handler.move(new_colorPicker_x, new_colorPicker_y)
#if self.appState.colorMode == "16":
# new_colorPicker_y = realmaxY - self.statusBar.colorPicker_bg_16.handler.height - 2
# new_colorPicker_x = realmaxX - self.statusBar.colorPicker_bg_16.handler.width
# self.statusBar.colorPicker_bg_16.handler.move(new_colorPicker_x, new_colorPicker_y)
#else:
# # Move the color picker to just above the status bar
# self.statusBar.colorPicker.handler.move(0, realmaxY - 10)
self.statusBarLineNum = statusBarLineNum
# resize window, tell the statusbar buttons
self.statusBar.menuButton.update_real_xy(x = statusBarLineNum)
self.statusBar.toolButton.update_real_xy(x = statusBarLineNum)
self.statusBar.animButton.update_real_xy(x = statusBarLineNum)
self.statusBar.colorPickerButton.update_real_xy(x = statusBarLineNum + 1)
if self.appState.showCharSetButton:
self.statusBar.charSetButton.update_real_xy(x = statusBarLineNum + 1)
if not self.appState.narrowWindow:
# Put character picker button left of character picker
self.statusBar.drawCharPickerButton.update_real_xy(x = statusBarLineNum + 1)
self.statusBar.drawCharPickerButton.update_real_xy(y = self.chMap_offset-11)
#if self.appState.colorMode == "256":
# self.statusBar.colorPickerButton.update_real_xy(x = statusBarLineNum + 1)
canvasSizeBar = f"[{self.mov.sizeX}x{self.mov.sizeY}]"
canvasSizeOffset = realmaxX - len(canvasSizeBar) - 1 # right of transport
self.addstr(statusBarLineNum, canvasSizeOffset, canvasSizeBar, curses.color_pair(mainColor))
frameBar = "F:%i/%i " % (self.mov.currentFrameNumber, self.mov.frameCount)
rangeBar = "R:%i-%i " % (self.appState.playbackRange[0], self.appState.playbackRange[1])
fpsBar = ":%i " % (self.opts.framerate)
delayBar = "D:%.2f " % (self.mov.currentFrame.delay)
# Ugly hardcoded locations. These should be handled in the GUI
# framework instead.
frameBar_offset = 2 + line_1_offset
fpsBar_offset = 13 + line_1_offset
fpsBar_minus_offset = fpsBar_offset + 4
delayBar_offset = 23 + line_1_offset
rangeBar_offset = 31 + line_1_offset
#chMap_offset = 35 # how far in to show the character map
if self.appState.narrowWindow:
if self.appState.colorMode == "16":
self.chMap_offset = 1 # how far in to show the character map
else:
self.chMap_offset = 7 # how far in to show the character map
else:
self.chMap_offset = realmaxX - 50 # how far in to show the character map
# > is hardcoded at 66. yeesh.
chMap_next_offset = self.chMap_offset + 31
# Move the draw button to under the frameBar for 16 color mode
#if self.appState.colorMode == "16":
# self.statusBar.drawCharPickerButton.update_real_xy(x = statusBarLineNum)
# self.statusBar.drawCharPickerButton.update_real_xy(y = frameBar_offset-5)
# if realmaxX < 87: # too small to show the draw character.
# self.statusBar.drawCharPickerButton.hide()
# else:
# self.statusBar.drawCharPickerButton.show()
#if self.appState.colorMode == "256":
# self.statusBar.drawCharPickerButton.show()
# Draw elements that aren't in the GUI framework
self.addstr(statusBarLineNum, frameBar_offset, frameBar, curses.color_pair(mainColor))
self.addstr(statusBarLineNum, fpsBar_offset, fpsBar, curses.color_pair(mainColor))
self.addstr(statusBarLineNum, delayBar_offset, delayBar, curses.color_pair(mainColor))
self.addstr(statusBarLineNum, rangeBar_offset, rangeBar, curses.color_pair(mainColor))
#if not self.appState.narrowWindow: # Wide big window
# self.addstr(statusBarLineNum, frameBar_offset, frameBar, curses.color_pair(mainColor))
# self.addstr(statusBarLineNum, fpsBar_offset, fpsBar, curses.color_pair(mainColor))
# self.addstr(statusBarLineNum, delayBar_offset, delayBar, curses.color_pair(mainColor))
# self.addstr(statusBarLineNum, rangeBar_offset, rangeBar, curses.color_pair(mainColor))
#else: # Narrow small window
# # Draw frame #. that's important.
# frameBar_offset = 0
# fpsBar_offset = frameBar_offset + len(frameBar) + 1
# self.addstr(statusBarLineNum, frameBar_offset, frameBar, curses.color_pair(mainColor))
# self.addstr(statusBarLineNum, fpsBar_offset, fpsBar, curses.color_pair(mainColor))
# Move char button to the left of frameBar_offset
if self.appState.debug:
cp = self.ansi.colorPairMap[(self.colorfg, self.colorbg)]
cp2 = self.colorpair
pairs = len(self.ansi.colorPairMap)
try:
extColors = curses.has_extended_color_support()
except:
extColors = False
colorValue = curses.color_content(self.colorfg)
debugstring = f"Fg: {self.colorfg}, bg: {self.colorbg}, cpairs: {cp}, {cp2}, pairs: {pairs}, ext: {extColors}, {colorValue}"
self.addstr(statusBarLineNum-1, 0, debugstring, curses.color_pair(mainColor))
debugstring2 = f"ButtonPress: {self.pressingButton}, topLine: {self.appState.topLine}, firstCol: {self.appState.firstCol}, resized: {resized}"
self.addstr(statusBarLineNum-2, 0, debugstring2, curses.color_pair(mainColor))
colorValue = curses.color_content(self.colorbg)
debugstring3= f"bg: {colorValue}"
self.addstr(statusBarLineNum-3, 0, debugstring3, curses.color_pair(mainColor))
# Draw FG and BG colors
if self.appState.colorMode == "256":
self.addstr(statusBarLineNum+1, 0, "FG:", curses.color_pair(clickColor) | curses.A_BOLD)
cp = self.ansi.colorPairMap[(self.colorfg, 0)]
self.addstr(statusBarLineNum+1, 3, self.appState.colorPickChar * 2, curses.color_pair(cp))
if self.appState.showBgColorPicker:
self.addstr(statusBarLineNum+1, 6, "BG:", curses.color_pair(clickColor) | curses.A_BOLD)
cp = self.ansi.colorPairMap[(1, self.colorbg)]
fillChar = ' '
self.addstr(statusBarLineNum+1, 9, fillChar * 2, curses.color_pair(cp))
# Draw character map for f1-f10 (block characters)
self.addstr(statusBarLineNum+1, self.chMap_offset-1, "<", curses.color_pair(clickColor) | curses.A_BOLD)
self.addstr(statusBarLineNum+1, self.chMap_offset+31, ">", curses.color_pair(clickColor) | curses.A_BOLD)
#if self.appState.charEncoding == "cp437":
# try:
# chMapString = self.chMapString.encode('cp437')
# except: # can't encode as cp437, so just render as blank
# chMapString = ' '
if self.colorfg > 8 and self.appState.colorMode == "16": # bright color
self.addstr(statusBarLineNum+1, self.chMap_offset, self.chMapString, curses.color_pair(self.colorpair) | curses.A_BOLD)
else: # normal color
self.addstr(statusBarLineNum+1, self.chMap_offset, self.chMapString, curses.color_pair(self.colorpair))
# draw current character set #
charSetNumberString = f"({self.charMapNumber+1}/{len(self.fullCharMap)})"
if self.appState.colorMode == "16": # put it to the right instead of the left, to make room for BG colors
self.addstr(statusBarLineNum+1, self.chMap_offset+len(self.chMapString)+2, charSetNumberString, curses.color_pair(mainColor))
#if self.appState.colorMode == 256:
else:
#self.addstr(statusBarLineNum+1, chMap_offset-16, charSetNumberString, curses.color_pair(mainColor))
self.addstr(statusBarLineNum+1, self.chMap_offset-8, charSetNumberString, curses.color_pair(mainColor))
#self.addstr(statusBarLineNum+1, chMap_offset+len(self.chMapString)+2, str(self.charMapNumber+1), curses.color_pair(mainColor))
# overlay draw function key names in normal color
y = 0
#for x in range(1,11):
# self.addstr(statusBarLineNum+1, chMap_offset+y, "F%i" % x, curses.color_pair(mainColor))
# y = y + 3
# draw 16-color picker
if self.appState.colorMode == "16" and self.realmaxX > self.appState.full_ui_width:
colorPickerFGOffset = 0
self.addstr(statusBarLineNum+1, colorPickerFGOffset, "FG:", curses.color_pair(mainColor))
#hiColor = 7 # for old way, using bold to get high colors
hiColor = 17 # for old way, using bold to get high colors
if self.appState.iceColors:
hiColor = 16
for c in range(1,hiColor):
cp = self.ansi.colorPairMap[(c, 0)]
if c > 8:
if c == self.colorfg:
self.addstr(statusBarLineNum+1, colorPickerFGOffset+2+c,'X', curses.color_pair(cp) | curses.A_BOLD) # block character
else:
self.addstr(statusBarLineNum+1, colorPickerFGOffset+2+c, self.appState.colorPickChar, curses.color_pair(cp) | curses.A_BOLD) # block character
else:
if c == self.colorfg:
if c == 1: # black fg
self.addstr(statusBarLineNum+1, colorPickerFGOffset+2+c,'X', curses.color_pair(mainColor))
else:
self.addstr(statusBarLineNum+1, colorPickerFGOffset+2+c,'X', curses.color_pair(cp)) # block character
else:
self.addstr(statusBarLineNum+1, colorPickerFGOffset+2+c, self.appState.colorPickChar, curses.color_pair(cp))
# bg color
colorPickerBGOffset = 21
self.addstr(statusBarLineNum+1, colorPickerBGOffset, "BG:", curses.color_pair(mainColor))
hiColor = 9 # for old way, using bold to get high colors
if self.appState.iceColors:
hiColor = 16
for c in range(1,hiColor):
cp = self.ansi.colorPairMap[(c, 0)]
if c == self.colorbg: #or (c == 8 and self.colorbg == 0):
#self.addstr(statusBarLineNum+1, colorPickerBGOffset+3+c, 'X', curses.color_pair(self.ansi.colorPairMap[(16, c)]))
if not self.appState.iceColors:
if c == 9: # black bg
self.addstr(statusBarLineNum+1, colorPickerBGOffset+3+c, 'X', curses.color_pair(mainColor))
else:
self.addstr(statusBarLineNum+1, colorPickerBGOffset+3+c, 'X', curses.color_pair(self.ansi.colorPairMap[(16, c)]))
else:
self.addstr(statusBarLineNum+1, colorPickerBGOffset+3+c, 'X', curses.color_pair(self.ansi.colorPairMap[(16, c)]))
else:
self.addstr(statusBarLineNum+1, colorPickerBGOffset+3+c, self.appState.colorPickChar, curses.color_pair(cp + 1))
# Draw x/y location/position
locationString = "(%i,%i)" % (self.xy[1]-1, self.xy[0])
locationStringOffset = realmaxX - len(locationString) - 1
self.addstr(statusBarLineNum+1, locationStringOffset, locationString, curses.color_pair(mainColor))
# Draw button indicator
if self.pressingButton:
self.addstr(statusBarLineNum + 1, locationStringOffset - 1, "*", curses.color_pair(3) | curses.A_BOLD)
else:
self.addstr(statusBarLineNum + 1, locationStringOffset - 1, " ", curses.color_pair(3) | curses.A_BOLD)
if self.realmaxX >= self.appState.full_ui_width:
# Draw Range, FPS and Delay buttons
self.addstr(statusBarLineNum, 13 + line_1_offset, "<", curses.color_pair(clickColor) | curses.A_BOLD) # FPS buttons
self.addstr(statusBarLineNum, 17 + line_1_offset, ">", curses.color_pair(clickColor) | curses.A_BOLD)
if self.appState.modified:
self.addstr(statusBarLineNum + 1, realmaxX - 1, "*", curses.color_pair(4) | curses.A_BOLD)
else:
self.addstr(statusBarLineNum + 1, realmaxX - 1, " ", curses.color_pair(4) | curses.A_BOLD)
if not self.playing:
self.addstr(statusBarLineNum, 2 + line_1_offset, "F", curses.color_pair(clickColor) | curses.A_BOLD) # Frame button
self.addstr(statusBarLineNum, 31 + line_1_offset, "R", curses.color_pair(clickColor) | curses.A_BOLD) # Range button
self.addstr(statusBarLineNum, 23 + line_1_offset, "D", curses.color_pair(clickColor) | curses.A_BOLD) # Delay button
# draw transport
transportString = "|< << |> >> >|"
transportOffset = realmaxX - len(transportString) - 9
self.transportOffset = transportOffset
if self.appState.narrowWindow: # small window, only show |>
transportOffset = realmaxX - 12
if self.playing:
playButtonString = "||"
else:
playButtonString = "|>"
self.addstr(statusBarLineNum, transportOffset, playButtonString, curses.color_pair(clickColor) | curses.A_BOLD)
else: # wide window, show full transport
if self.playing:
transportString = "|< << || >> >|"
self.addstr(statusBarLineNum, transportOffset, transportString, curses.color_pair(mainColor))
self.addstr(statusBarLineNum, transportOffset+6, "||", curses.color_pair(clickColor) | curses.A_BOLD)
else:
transportString = "|< << |> >> >|"
self.addstr(statusBarLineNum, transportOffset, transportString, curses.color_pair(clickColor) | curses.A_BOLD)
# Draw the new status bar
if self.commandMode:
# When we hit esc in the canvas, show tooltips etc.
self.addstr(statusBarLineNum, realmaxX - 1, "*", curses.color_pair(2) | curses.A_BOLD)
self.statusBar.showToolTips()
else:
self.statusBar.hideToolTips()
self.addstr(statusBarLineNum, realmaxX - 1, " ", curses.color_pair(2) | curses.A_BOLD)
# More offsets for the tooltips - for transport buttons
trans_play_offset = transportOffset + 6
trans_prev_offset = transportOffset + 3
trans_next_offset = transportOffset + 10
# Update tooltip locations for free floating tooltips
frameBar_tip = self.statusBar.other_tooltips.get_tip("g")
frameBar_tip.set_location(row = statusBarLineNum, column = frameBar_offset)
fpsBar_plus_tip = self.statusBar.other_tooltips.get_tip("-")
fpsBar_plus_tip.set_location(row = statusBarLineNum, column = fpsBar_offset)
fpsBar_minus_tip = self.statusBar.other_tooltips.get_tip("+")
fpsBar_minus_tip.set_location(row = statusBarLineNum, column = fpsBar_minus_offset)
delayBar_tip = self.statusBar.other_tooltips.get_tip("D")
delayBar_tip.set_location(row = statusBarLineNum, column = delayBar_offset)
rangeBar_tip = self.statusBar.other_tooltips.get_tip("R")
rangeBar_tip.set_location(row = statusBarLineNum, column = rangeBar_offset)
colorPicker_tip = self.statusBar.other_tooltips.get_tip("c")
colorPicker_tip.set_location(row = statusBarLineNum + 1, column = 2)
prevChMap_tip = self.statusBar.other_tooltips.get_tip("[")
prevChMap_tip.set_location(row = statusBarLineNum + 1, column = self.chMap_offset - 1)
nextChMap_tip = self.statusBar.other_tooltips.get_tip("]")
nextChMap_tip.set_location(row = statusBarLineNum + 1, column = chMap_next_offset)
play_tip = self.statusBar.other_tooltips.get_tip("p")
play_tip.set_location(row = statusBarLineNum, column = trans_play_offset)
prev_tip = self.statusBar.other_tooltips.get_tip("j")
prev_tip.set_location(row = statusBarLineNum, column = trans_prev_offset)
next_tip = self.statusBar.other_tooltips.get_tip("k")
next_tip.set_location(row = statusBarLineNum, column = trans_next_offset)
# hide un-used tool tips in narrow view
if self.appState.narrowWindow:
play_tip.alwaysHidden = True
prev_tip.alwaysHidden = True
next_tip.alwaysHidden = True
else:
play_tip.alwaysHidden = False
prev_tip.alwaysHidden = False
next_tip.alwaysHidden = False
#if self.appState.colorMode == "16":
# colorPicker_tip.hide()
self.statusBar.draw()
# if cursor is outside of canvas, fix it
bottomLine = self.realmaxY - 3 + self.appState.topLine
if self.xy[0] > self.mov.sizeY - 1: # cursor is past bottom of the canvas
self.xy[0] = self.mov.sizeY - 1
if self.xy[1] > self.mov.sizeX: # cursor is past right edge of the canvas
self.xy[1] = self.mov.sizeX
# if it's off screen.. fix that, too
if self.xy[0] - self.appState.topLine > realmaxY - 3:
self.xy[0] = realmaxY - 3 + self.appState.topLine
if self.xy[1] < 0:
self.xy[1] = 1
self.move(self.xy[0], self.xy[1] - 1)
# Draw fill character button with proper (preview) colors
if not self.statusBar.drawCharPickerButton.hidden:
drawChar_line = self.statusBar.drawCharPickerButton.realX
drawChar_col = self.statusBar.drawCharPickerButton.realY + 1
self.addstr(drawChar_line, drawChar_col, self.appState.drawChar, curses.color_pair(self.colorpair))
if resized:
self.refresh()
self.hardRefresh()
def window_big_enough_for_colors(self):
# Returns true if window is either tall enough or wide enough
# to fit a palette to the right of or below the canvas
returnValue = True
realmaxY,realmaxX = self.realstdscr.getmaxyx()
if realmaxX < self.mov.sizeX + self.appState.sideBar_minimum_width: # I'm not wide enough
if realmaxY - self.appState.bottomBar_minimum_height < self.mov.sizeY: # I'm not tall enough
returnValue = False # and gosh darnit, pepple like me.
if realmaxY - self.appState.bottomBar_minimum_height < self.mov.sizeY:
if realmaxX < self.mov.sizeX + self.appState.sideBar_minimum_width:
returnValue = False
#debugString = f"big enough: {returnValue}"
#self.addstr(realmaxY - 3, 0, debugString, curses.color_pair(self.appState.theme['clickHighlightColor']) | curses.A_BOLD)
return returnValue
def clickedUndo(self):
self.undo.undo()
if self.appState.playbackRange[1] > self.mov.frameCount:
#self.appState.playbackRange = (start, stop)
self.setPlaybackRange(1, self.mov.frameCount)
self.hardRefresh()
def clickedRedo(self):
self.undo.redo()
if self.appState.playbackRange[1] > self.mov.frameCount:
self.setPlaybackRange(1, self.mov.frameCount)
self.hardRefresh()
def clickHighlight(self, pos, buttonString, bar='top'): # Visual feedback
# example: self.clickHighlight(52, "|>")
# Highlight clicked item at "pos" by drawing it as "str" in bright white or yellow, sleep a moment,
# then back to green
if bar == 'top':
y = self.statusBarLineNum
if bar == 'bottom':
y = self.statusBarLineNum + 1
self.addstr(y, pos, buttonString, curses.color_pair(self.appState.theme['clickHighlightColor']) | curses.A_BOLD)
curses.curs_set(0) # turn off cursor
self.stdscr.refresh()
time.sleep(0.2)
self.addstr(y, pos, buttonString, curses.color_pair(self.appState.theme['clickColor']))
curses.curs_set(1) # turn on cursor
def mainLoop(self):
self.metaKey = 0
self.commandMode = False
cursorMode = self.appState.cursorMode
mouseX, mouseY = 0, 0
self.pressingButton = False
self.drawStatusBar() # to make sure the inital state looks correct
intense_burning = ['idkfa']
sumbitch = []
sumbitch_len = 25
curses.noecho()
while 1: # Real "main loop" - get user input, aka "edit mode"
self.testWindowSize()
# print statusbar stuff
self.drawStatusBar()
self.move(self.xy[0], self.xy[1] - 1) # move cursor to the right
# spot for refresh
curses.panel.update_panels()
self.stdscr.refresh()
c = self.stdscr.getch() # non-wide characters, for old ncurses
#c = ord(self.stdscr.get_wch()) # wide characters, for ncursesw
self.testWindowSize()
#if c in ["\x1b\x1b\x5b\x42"]: self.notify("alt-down")
if self.metaKey == 1:
self.pressingButton = False
if self.pushingToClip:
self.pushingToClip = False
if cursorMode != "Draw" and cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if c == 111: # alt-o - open
self.openFromMenu() # as if we clicked menu->open
#load_filename = self.openFilePicker()
#if load_filename: # if not False
# self.clearCanvas(prompting=False)
# self.loadFromFile(load_filename, 'dur')
# self.move_cursor_topleft()
elif c == 115: # alt-s - save
self.save()
c = None
elif c == 113: # alt-q - quit
self.safeQuit()
c = None
elif c in [104, 63]: # alt-h - help
self.showHelp()
c = None
#elif c in [98, curses.KEY_LEFT]: # alt-left - prev bg color
elif c in [curses.KEY_LEFT]: # alt-left - prev bg color (for 16)
if self.appState.colorMode == "16":
self.prevBgColor()
elif self.appState.colorMode == "256":
self.prevFgColor()
c = None
#elif c in [102, curses.KEY_RIGHT]: # alt-right - next bg color (for 16)
elif c in [curses.KEY_RIGHT]: # alt-right - next fg color
if self.appState.colorMode == "16":
self.nextBgColor()
elif self.appState.colorMode == "256":
self.nextFgColor()
c = None
elif c in [curses.KEY_DOWN, "\x1b\x1b\x5b\x42"]: # alt-down - prev bg color
if self.appState.colorMode == "16":
self.prevFgColor()
elif self.appState.colorMode == "256":
self.statusBar.colorPicker.handler.move_down_256()
c = None
elif c == curses.KEY_UP: # alt-up - next bg color
if self.appState.colorMode == "16":
self.nextFgColor()
elif self.appState.colorMode == "256":
self.statusBar.colorPicker.handler.move_up_256()
c = None
elif c == 91 or c == 339: # alt-[ (91) or alt-pgup (339). previous character set
self.prevCharSet()
elif c == 93 or c == 338: # alt-] or alt-pgdown, next character set
self.nextCharSet()
elif c == 83: # alt-S - pick a character set or unicode block
self.showCharSetPicker()
elif c == 44: # alt-, - erase/pop current column in frame
self.delCol()
elif c == 46: # alt-. - insert column in frame
self.addCol()
elif c == 62: # alt-> - insert column in canvas
self.addColToCanvas()
elif c == 60: # alt-< - delete column from canvas
self.delColFromCanvas()
elif c == 34: # alt-" - insert line in canvas
self.addLineToCanvas()
elif c == 58: # alt-: - erase line from canvas
self.delLineFromCanvas()
elif c == 47: # alt-/ - insert line
self.addLine()
elif c == 39: # alt-' - erase line
self.delLine()
elif c == 121 or c == ord('u'): # alt-y - Eyedrop, alt-u is what Aciddraw used
self.eyeDrop(self.xy[1] - 1, self.xy[0]) # cursor position
elif c == ord('P'): # alt-P - pick up charcter
self.pickUpDrawingChar(self.xy[1] - 1, self.xy[0])
#self.notify(f"Picked up character: {self.appState.drawChar}")
elif c == ord(' '): # alt-space - insert drawing character
drawChar = self.appState.drawChar
x_param = self.xy[1]
y_param = self.xy[0]
self.insertChar(ord(drawChar), fg=self.colorfg, bg=self.colorbg, x=x_param, y=y_param, moveCursor=True, pushUndo=True)
elif c == ord('l'): # alt-l - color under cursor
self.insertColor(fg=self.colorfg, bg=self.colorbg, pushUndo=True)
elif c == ord('L'): # alt-L - search and replace color
self.replaceColorUnderCursor()
elif c == 73: # alt-I - Character Inspector
self.showCharInspector()
elif c == 105: # alt-i - File/Canvas Information
self.clickedInfoButton()
#if self.appState.sideBarShowing:
# self.toggleShowFileInformation()
#else:
# self.showFileInformation(notify=True)
elif c == 109 or c == 102: # alt-m or alt-f - load menu
self.commandMode = False
self.openMenu("File")
elif c == ord('a'): # alt-a - Animation menu
self.commandMode = False
self.openMenu("Anim")
elif c == 116: # or c =- 84: # alt-t or alt-T - mouse tools menu
self.commandMode = False
self.openMenu("Mouse Tools")
#self.statusBar.toolButton.on_click()
elif c == 99: # alt-c - color picker
self.commandMode = False
#if self.appState.colorMode == "256":
#if self.appState.sideBarShowing:
# self.statusBar.colorPicker.switchTo()
#else:
# self.statusBar.colorPickerButton.on_click()
#self.selectColorPicker()
self.statusBar.colorPickerButton.on_click()
# Animation Keystrokes
elif c == 68: #alt-D - set delay for current frame
self.getDelayValue()
elif c == 107: # alt-k - next frame
self.mov.nextFrame()
self.refresh()
elif c == 106: # alt-j or alt-j - previous frame
self.mov.prevFrame()
self.refresh()
elif c == 103: # alt-g - go to frame
self.gotoFrameGetInput()
elif c == 67: # alt-C - clear canvas/new
self.clearCanvas(prompting=True)
elif c == 110 or c == 14: # alt-n - clone to new frame
self.cloneToNewFrame()
elif c == 78: # alt-N (shift-alt-n) - new empty frame
self.appendEmptyFrame()
elif c == 77: # alt-M - move current frame
self.moveCurrentFrame()
elif c == 100: # alt-d - delete current frame
self.deleteCurrentFrame()
elif c == 125: # alt-} - shift frames right
self.shiftMovieRight()
elif c == 123: # alt-{ - shift frames left
self.shiftMovieLeft()
elif c == 122: # alt-z = undo
self.clickedUndo()
elif c == 114: # alt-r = redo
self.clickedRedo()
elif c == ord('F'): # alt-F, find/search
self.searchForStringPrompt()
elif c == 118: # alt-v - paste
# Paste from the clipboard
if self.clipBoard: # If there is something in the clipboard
self.askHowToPaste()
#self.pasteFromClipboard()
elif c == ord('V'): # alt-V, View mode
self.enterViewMode()
elif c == 82: # alt-R = set playback range
self.getPlaybackRange()
elif c == 112: # esc-p - start playing, any key exits
self.commandMode = False
self.metaKey = 0
self.startPlaying()
elif c in [61, 43]: # esc-= and esc-+ - fps up
self.increaseFPS()
elif c in [45]: # esc-- (alt minus) - fps down
self.decreaseFPS()
elif c in [75]: # alt-K = start marking selection
startPoint=(self.xy[0] + self.appState.topLine, self.xy[1] + self.appState.firstCol)
self.startSelecting(firstkey=c) # start selecting text
elif c in [ord('1')]: # esc-1 copy of F1 - insert extended character
self.insertChar(self.chMap['f1'], fg=self.colorfg, bg=self.colorbg)
#self.hardRefresh()
self.stdscr.refresh()
c = None
elif c in [ord('2')]: # esc-2 copy of F2 - insert extended character
self.insertChar(self.chMap['f2'], fg=self.colorfg, bg=self.colorbg)
#self.hardRefresh()
#self.refresh()
#self.refresh(refreshScreen=False)
self.stdscr.refresh()
c = None
elif c in [ord('3')]: # F3 - insert extended character
self.insertChar(self.chMap['f3'], fg=self.colorfg, bg=self.colorbg)
#self.hardRefresh()
self.stdscr.refresh()
c = None
elif c in [ord('4')]: # F4 - insert extended character
self.insertChar(self.chMap['f4'], fg=self.colorfg, bg=self.colorbg)
#self.refresh()
self.stdscr.refresh()
c = None
elif c in [ord('5')]: # F5 - insert extended character
self.insertChar(self.chMap['f5'], fg=self.colorfg, bg=self.colorbg)
#self.hardRefresh()
self.stdscr.refresh()
c = None
elif c in [ord('6')]: # F6 - insert extended character
self.insertChar(self.chMap['f6'], fg=self.colorfg, bg=self.colorbg)
#self.hardRefresh()
self.stdscr.refresh()
c = None
elif c in [ord('7')]: # F7 - insert extended character
self.insertChar(self.chMap['f7'], fg=self.colorfg, bg=self.colorbg)
#self.hardRefresh()
self.stdscr.refresh()
c = None
elif c in [ord('8')]: # F8 - insert extended character
self.insertChar(self.chMap['f8'], fg=self.colorfg, bg=self.colorbg)
#self.hardRefresh()
self.stdscr.refresh()
c = None
elif c in [ord('9')]: # F9 - insert extended character
self.insertChar(self.chMap['f9'], fg=self.colorfg, bg=self.colorbg)
#self.hardRefresh()
self.stdscr.refresh()
c = None
elif c in [ord('0')]: # F10 - insert extended character
self.insertChar(self.chMap['f10'], fg=self.colorfg, bg=self.colorbg)
#self.hardRefresh()
self.stdscr.refresh()
c = None
elif c == 27: # 2nd esc byte - possibly alt-arrow.
# eg: alt-down: 27 27 91 66 or \x1b\x1b\x5b\x42
c = self.stdscr.getch()
if c == 91: # 3rd byte (\x5b) in arrow key sequence
c = self.stdscr.getch()
if c == 65: # real alt-up, not esc-up
if self.appState.colorMode == "16":
self.nextFgColor()
elif self.appState.colorMode == "256":
self.statusBar.colorPicker.handler.move_up_256()
c = None
elif c == 66: # real alt-down, not esc-down
if self.appState.colorMode == "16":
self.prevFgColor()
elif self.appState.colorMode == "256":
self.statusBar.colorPicker.handler.move_down_256()
c = None
elif c == 67: # real alt-right, not esc-right
if self.appState.colorMode == "16":
self.nextBgColor()
elif self.appState.colorMode == "256":
self.nextFgColor()
c = None
elif c == 68: # real alt-left, not esc-left
if self.appState.colorMode == "16":
self.prevBgColor()
elif self.appState.colorMode == "256":
self.prevFgColor()
c = None
self.pressingButton = False
if cursorMode != "Draw" and cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if self.pushingToClip:
self.pushingToClip = False
else:
self.pressingButton = False
if cursorMode != "Draw" and cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if self.pushingToClip:
self.pushingToClip = False
if self.appState.debug:
if c == ord('X'): # esc-X - drop into pdb debugger
self.jumpToPythonConsole()
else:
self.notify("keystroke: %d" % c) # alt-unknown
self.commandMode = False
self.metaKey = 0
c = None
# Meta key (alt) was not pressed, so look for non-meta chars
if c == 27:
self.metaKey = 1
self.commandMode = True
self.pressingButton = False
if self.pushingToClip:
self.pushingToClip = False
if cursorMode != "Draw" and cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
self.appState.renderMouseCursor = False
c = None
if c == 24: self.safeQuit() # ctrl-x
elif c == 15: # ctrl-o - open # holy crap, this is still in here? lol
load_filename = self.openFilePicker()
if load_filename: # if not False
self.clearCanvas(prompting=False)
self.loadFromFile(load_filename, 'dur')
self.move_cursor_topleft()
c = None
elif c == 23: # ctrl-w - save
self.save()
c = None
elif c == 12: # ctrl-l - harder refresh
self.stdscr.clear()
self.hardRefresh()
c = None
elif c in [10, 13, curses.KEY_ENTER]: # enter (10 if we
self.move_cursor_enter()
elif c in [263, 127]: # backspace
self.backspace()
elif c in [330]: # delete
self.deleteKeyPop()
elif c in [383]: # shift-delete - delete from opposite direction
self.reverseDelete()
elif c in [9, 353]: # 9 = tab, 353 = shift-tab
#if self.appState.colorMode == "256":
#self.statusBar.colorPickerButton.on_click()
#self.selectColorPicker()
self.selectColorPicker()
elif c in [339, curses.KEY_PPAGE]: # page up
self.move_cursor_pgup()
elif c in [338, curses.KEY_NPAGE]: # page down
self.move_cursor_pgdown()
elif c in [1, curses.KEY_HOME]: # ctrl-a or home
self.move_cursor_home()
elif c in [5, curses.KEY_END]: # ctrl-e or end
self.move_cursor_end()
elif c in [curses.KEY_F1]: # F1 - insert extended character
self.insertChar(self.chMap['f1'], fg=self.colorfg, bg=self.colorbg)
c = None
elif c in [curses.KEY_F2]: # F2 - insert extended character
self.insertChar(self.chMap['f2'], fg=self.colorfg, bg=self.colorbg)
c = None
elif c in [curses.KEY_F3]: # F3 - insert extended character
self.insertChar(self.chMap['f3'], fg=self.colorfg, bg=self.colorbg)
c = None
elif c in [curses.KEY_F4]: # F4 - insert extended character
self.insertChar(self.chMap['f4'], fg=self.colorfg, bg=self.colorbg)
c = None
elif c in [curses.KEY_F5]: # F5 - insert extended character
self.insertChar(self.chMap['f5'], fg=self.colorfg, bg=self.colorbg)
c = None
elif c in [curses.KEY_F6]: # F6 - insert extended character
self.insertChar(self.chMap['f6'], fg=self.colorfg, bg=self.colorbg)
c = None
elif c in [curses.KEY_F7]: # F7 - insert extended character
self.insertChar(self.chMap['f7'], fg=self.colorfg, bg=self.colorbg)
c = None
elif c in [curses.KEY_F8]: # F8 - insert extended character
self.insertChar(self.chMap['f8'], fg=self.colorfg, bg=self.colorbg)
c = None
elif c in [curses.KEY_F9]: # F9 - insert extended character
self.insertChar(self.chMap['f9'], fg=self.colorfg, bg=self.colorbg)
c = None
elif c in [curses.KEY_F10]: # F10 - insert extended character
self.insertChar(self.chMap['f10'], fg=self.colorfg, bg=self.colorbg)
c = None
elif c == curses.KEY_LEFT: # left - move cursor right a character
self.move_cursor_left()
elif c == curses.KEY_RIGHT: # right - move cursor right
self.move_cursor_right()
elif c == curses.KEY_UP: # up - move cursor up
self.move_cursor_up()
elif c == curses.KEY_DOWN: # down - move curosr down
self.move_cursor_down()
elif c in [339, curses.KEY_HOME]: # 339 = home. not in xterm :'(
self.move_cursor_home()
elif c in [338, curses.KEY_END]: # 338 = end
self.move_cursor_end()
elif c != curses.KEY_MOUSE and self.pressingButton:
self.pressingButton = False
if self.pushingToClip:
self.pushingToClip = False
cursorMode = self.appState.cursorMode
if cursorMode != "Draw" and cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
elif c == curses.KEY_MOUSE: # We are not playing
try:
_, mouseX, mouseY, _, mouseState = curses.getmouse()
b1_press = mouseState & curses.BUTTON1_PRESSED
b1_release = mouseState & curses.BUTTON1_RELEASED
b1_click = mouseState & curses.BUTTON1_CLICKED
b1_dclick = mouseState & curses.BUTTON1_DOUBLE_CLICKED
self.appState.mouse_col = mouseX
self.appState.mouse_line = mouseY
self.appState.renderMouseCursor = True
if mouseState > 2: # probably click-dragging. We want an instant response, so...
pass
#self.pressingButton = True
#print('\033[?1003h') # enable mouse tracking with the XTERM API
#print('\033[?1003l') # disable mouse reporting
#curses.mousemask(1)
#curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if mouseState == 1:
self.pressingButton = False
if self.pushingToClip:
self.pushingToClip = False
cursorMode = self.appState.cursorMode
if cursorMode != "Draw" and cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if self.pushingToClip:
self.pushingToClip = False
#self.notify("Released from drag, hopefully.")
if self.appState.debug:
# clear mouse state lines
winHeight,winWidth = self.realstdscr.getmaxyx()
blank_line = " " * winWidth
self.addstr(self.statusBarLineNum-3, 0, blank_line, curses.color_pair(3) | curses.A_BOLD)
self.addstr(self.statusBarLineNum-4, 0, blank_line, curses.color_pair(3) | curses.A_BOLD)
# print mouse state
mouseDebugString = f"mX: {mouseX}, mY: {mouseY}, mState: {mouseState}, press:{b1_press} rel:{b1_release} clk:{b1_click} dclk: {b1_dclick}"
self.addstr(self.statusBarLineNum-4, 0, mouseDebugString, curses.color_pair(3) | curses.A_BOLD)
#self.addstr(self.statusBarLineNum-5, 0, mouseDebugStates, curses.color_pair(2) | curses.A_BOLD)
except:
pass
#if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX \
# and mouseY + self.appState.topLine < self.appState.topLine + self.statusBarLineNum:
if mouseY + self.appState.topLine < self.mov.sizeY and mouseX + self.appState.firstCol < self.mov.sizeX:
# we're in the canvas, not playing
if mouseState & curses.BUTTON1_PRESSED:
if not self.pressingButton:
self.pressingButton = True
print('\033[?1003h') # enable mouse tracking with the XTERM API
self.hardRefresh()
if not self.pushingToClip:
cmode = self.appState.cursorMode
if cmode == "Draw" or cmode == "Paint" or cmode == "Color" or cmode == "Erase":
self.undo.push()
self.pushingToClip = True
elif mouseState & curses.BUTTON1_RELEASED:
if self.pressingButton:
self.pressingButton = False
if self.pushingToClip:
self.pushingToClip = False
cursorMode = self.appState.cursorMode
if cursorMode != "Draw" and cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if cursorMode == "Draw" and cursorMode == "Paint":
self.enableMouseReporting()
if self.pushingToClip:
self.pushingToClip = False
self.refresh()
#self.stdscr.redrawwin()
if not self.appState.hasMouseScroll:
curses.BUTTON5_PRESSED = 0
curses.BUTTON4_PRESSED = 0
try:
curses.BUTTON5_PRESSED
except:
curses.BUTTON5_PRESSED = 0
try:
curses.BUTTON4_PRESSED
except:
curses.BUTTON4_PRESSED = 0
if mouseState & curses.BUTTON4_PRESSED: # wheel up
if self.appState.scrollColors:
self.nextFgColor()
else:
self.move_cursor_up()
elif mouseState & curses.BUTTON5_PRESSED: # wheel down
if self.appState.scrollColors:
self.prevFgColor()
else:
self.move_cursor_down()
elif self.appState.cursorMode == "Move": # select mode/move the cursor
if self.pressingButton:
self.xy[1] = mouseX + 1 + self.appState.firstCol # set cursor position
self.xy[0] = mouseY + self.appState.topLine
elif self.appState.cursorMode == "Draw":
if self.pressingButton:
if self.appState.debug:
self.addstr(self.statusBarLineNum-2, 20, "Draw triggered.", curses.color_pair(6) | curses.A_BOLD)
#self.notify("Draw triggered.")
# also set cursor position - not anymore.
#self.xy[1] = mouseX + 1 # set cursor position
#self.xy[0] = mouseY + self.appState.topLine
# Insert the selected character.
drawChar = self.appState.drawChar
try:
x_param = mouseX + 1 + self.appState.firstCol
y_param = mouseY + self.appState.topLine
self.insertChar(ord(drawChar), fg=self.colorfg, bg=self.colorbg, x=x_param, y=y_param, moveCursor=False, pushUndo=False)
except IndexError:
self.notify(f"Error, debug info: x={x_param}, y={y_param}, topLine={self.appState.topLine}, mouseX={mouseX}, mouseY={mouseY}", pause=True)
#self.refresh()
else:
if self.appState.debug:
self.addstr(self.statusBarLineNum-2, 20, "Draw untriggered.", curses.color_pair(5) | curses.A_BOLD)
elif self.appState.cursorMode == "Paint": # Draw Brush under cursor
if self.pressingButton:
if self.appState.debug:
self.addstr(self.statusBarLineNum-2, 20, "Paint triggered.", curses.color_pair(6) | curses.A_BOLD)
# Paint brush onto canvas
#drawChar = self.appState.drawChar
painting = True
if not self.appState.brush: # if no brush is set...
painting = False # don't paint.
if painting:
if self.appState.debug:
self.addstr(self.statusBarLineNum-2, 20, "Paint painting.", curses.color_pair(6) | curses.A_BOLD)
try:
x_param = mouseX + 1 + self.appState.firstCol
y_param = mouseY + self.appState.topLine
#self.insertChar(ord(drawChar), fg=self.colorfg, bg=self.colorbg, x=x_param, y=y_param, moveCursor=False, pushUndo=False)
self.pasteFromClipboard(startPoint = [y_param, x_param], clipBuffer=self.appState.brush, transparent=True, pushUndo=False)
except IndexError:
self.notify(f"Error, debug info: x={x_param}, y={y_param}, topLine={self.appState.topLine}, mouseX={mouseX}, mouseY={mouseY}", pause=True)
self.refresh()
else:
if self.appState.debug:
self.addstr(self.statusBarLineNum-2, 20, "Paint untriggered.", curses.color_pair(5) | curses.A_BOLD)
elif self.appState.cursorMode == "Color": # Change the color under the cursor
if self.pressingButton:
# also set cursor position
self.xy[1] = mouseX + 1 + self.appState.firstCol # set cursor position
self.xy[0] = mouseY + self.appState.topLine
self.insertColor(fg=self.colorfg, bg=self.colorbg, x=mouseX+1 + self.appState.firstCol, y=mouseY + self.appState.topLine, pushUndo=False)
self.refresh()
elif self.appState.cursorMode == "Erase": # Erase character under the cursor
# also set cursor position
if self.pressingButton:
self.xy[1] = mouseX + 1 + self.appState.firstCol# set cursor position
self.xy[0] = mouseY + self.appState.topLine
color_fg = self.appState.defaultFgColor
color_bg = self.appState.defaultBgColor
self.insertChar(ord(' '), fg=color_fg, bg=color_bg, x=mouseX + self.appState.firstCol, y=mouseY + self.appState.topLine, pushUndo=False)
elif self.appState.cursorMode == "Eyedrop": # Change the color under the cursor
self.eyeDrop(mouseX + self.appState.firstCol, mouseY + self.appState.topLine)
self.statusBar.setCursorModeMove()
self.drawStatusBar()
elif self.appState.cursorMode == "Select": # this does not exist lol
self.xy[1] = mouseX + 1 + self.appState.firstCol # set cursor position
self.xy[0] = mouseY + self.appState.topLine
self.startSelecting(mouse=True)
# else, not in the canvas
elif self.pressingButton:
self.pressingButton = False
if self.pushingToClip:
self.pushingToClip = False
if self.appState.cursorMode != "Draw" and self.appState.cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
#self.mov.currentFrame.newColorMap[self.xy[0]][self.xy[1] - 1] = [self.colorfg, self.colorbg]
if not self.appState.hasMouseScroll:
curses.BUTTON5_PRESSED = 0
curses.BUTTON4_PRESSED = 0
try:
curses.BUTTON5_PRESSED
except:
curses.BUTTON5_PRESSED = 0
try:
curses.BUTTON4_PRESSED
except:
curses.BUTTON4_PRESSED = 0
if mouseState & curses.BUTTON1_PRESSED or mouseState & curses.BUTTON4_PRESSED or mouseState & curses.BUTTON5_PRESSED or b1_press > 0 or mouseState == curses.BUTTON1_DOUBLE_CLICKED:
#print('\033[?1003h')
#self.notify("Farfenugen")
realmaxY,realmaxX = self.realstdscr.getmaxyx()
cmode = self.appState.cursorMode
#if mouseState & curses.BUTTON1_PRESSED:
# self.gui.got_click("Click", mouseX, mouseY)
# If we clicked in the status bar:
if mouseX < realmaxX and mouseY in [self.statusBarLineNum, self.statusBarLineNum+1]: # we clicked on the status bar somewhere..
if mouseState & curses.BUTTON1_PRESSED:
#if cmode == "Draw" or cmode == "Paint" or cmode == "Color":
self.disableMouseReporting()
self.gui.got_click("Click", mouseX, mouseY)
if cmode == "Draw" or cmode == "Paint":
self.enableMouseReporting()
# Add stuff here to take mouse 'commands' like clicking
# play/next/etc on transport, or clicking "start button"
if mouseY == self.statusBarLineNum: # clicked upper bar
offset = self.line_1_offset # line 1 of the status bar
tOffset = self.transportOffset
if not self.appState.narrowWindow:
if mouseX in [tOffset + 6, tOffset + 7]: # clicked play button
self.clickHighlight(tOffset + 6, "|>")
self.startPlaying()
self.metaKey = 0
elif mouseX in [tOffset + 3, tOffset + 4]: # goto prev frame
self.clickHighlight(tOffset + 3, "<<")
self.mov.prevFrame()
elif mouseX in [tOffset + 9, tOffset + 10]: # goto next frame
self.clickHighlight(tOffset + 9, ">>")
self.mov.nextFrame()
elif mouseX in [tOffset, tOffset + 1]: # goto first frame
self.clickHighlight(tOffset, "|<")
self.mov.gotoFrame(1)
elif mouseX in [tOffset + 12, tOffset + 13]: # goto last frame
self.clickHighlight(tOffset + 12, ">|")
self.mov.nextFrame()
self.mov.gotoFrame(self.mov.frameCount)
if mouseX == 13 + offset: # clicked FPS down
self.clickHighlight(13 + offset, "<")
self.decreaseFPS()
elif mouseX == 17 + offset: # clicked FPS up
self.clickHighlight(17 + offset, ">")
self.increaseFPS()
elif mouseX == 23 + offset: # clicked Delay button
self.clickHighlight(23 + offset, "D")
self.getDelayValue()
elif mouseX == 31 + offset: # clicked Range button
self.clickHighlight(31 + offset, "R")
self.getPlaybackRange()
elif mouseX == 2 + offset: # clicked Frame button
self.clickHighlight(2 + offset, "F")
self.gotoFrameGetInput()
elif mouseY == self.statusBarLineNum+1: # clicked bottom bar
if self.appState.colorMode == "16" and self.realmaxX >= self.appState.full_ui_width:
if mouseX in range(3,19): # clicked a fg color
fg = mouseX - 2
self.setFgColor(fg)
elif mouseX in range(25,33): # clicked a bg color
bg = mouseX - 24
self.setBgColor(bg)
char_area_start = self.chMap_offset
char_area_end = self.chMap_offset+len(self.chMapString)
if mouseX == self.chMap_offset + len(self.chMapString): # clicked next character set
self.clickHighlight(self.chMap_offset + len(self.chMapString), ">", bar='bottom')
self.nextCharSet()
elif mouseX == self.chMap_offset - 1: # clicked previous character set
self.clickHighlight(self.chMap_offset - 1, "<", bar='bottom')
self.prevCharSet()
elif mouseX in range(char_area_start, char_area_end):
self.clickedChMap(mouseX, mouseY)
elif self.appState.debug:
self.notify("bottom bar. " + str([mouseX, mouseY]))
else:
if self.appState.debug:
self.notify(str([mouseX, mouseY]))
# If we clicked in the sidebar area, aka to the right of the canvas
# and above the status bar:
if self.appState.sideBarEnabled:
# If we're in the right toolbar sort of area
if mouseX >= self.appState.sideBarColumn and mouseY < self.statusBarLineNum:
#if self.appState.colorMode == "256":
# Tell the color picker to respond if the click is in its area:
# self.statusBar.colorPicker.handler.gotClick(mouseX, mouseY)
if mouseState == curses.BUTTON1_DOUBLE_CLICKED:
if self.appState.colorMode == "16": # set BG color
self.statusBar.colorPicker.handler.gotDoubleClick(mouseX, mouseY)
else:
self.statusBar.colorPicker.handler.gotClick(mouseX, mouseY)
#elif mouseState & curses.BUTTON1_RELEASED:
# pass
#print('\033[?1003l')
#curses.mousemask(1)
#curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if mouseState == curses.BUTTON1_CLICKED:
self.pressingButton = False
if cursorMode != "Draw" and cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if self.pushingToClip:
self.pushingToClip = False
realmaxY,realmaxX = self.realstdscr.getmaxyx()
cmode = self.appState.cursorMode
#if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX \
if mouseY + self.appState.topLine < self.appState.topLine + self.statusBarLineNum and mouseX + self.appState.firstCol < self.mov.sizeX: # we're in the canvas
if cmode == "Draw" or cmode == "Color" or cmode == "Erase" or cmode == "Paint":
self.undo.push()
else: # Not in the canvas, so give the GUI a click
#pass
if cmode == "Draw" or cmode == "Paint":
self.disableMouseReporting()
self.gui.got_click("Click", mouseX, mouseY)
if cmode == "Draw" or cmode == "Paint":
self.enableMouseReporting()
# If we clicked in the sidebar area, aka to the right of the canvas
# and above the status bar:
if self.appState.sideBarEnabled:
if mouseX >= self.appState.sideBarColumn and mouseY < self.statusBarLineNum:
#if self.appState.colorMode == "256":
# Tell the color picker to respond if the click is in its area:
self.statusBar.colorPicker.handler.gotClick(mouseX, mouseY)
elif c in [curses.KEY_SLEFT, curses.KEY_SRIGHT, 337, 336, 520, 513]:
# 337 and 520 - shift-up, 336 and 513 = shift-down
# shift-up, shift-down, shift-left and shift-right = start selecting text block
# shift-up and shift-down not defined in ncurses :(
# doesn't seem to work in screen?
startPoint=(self.xy[0] + self.appState.topLine, self.xy[1] + self.appState.firstCol)
self.startSelecting(firstkey=c) # start selecting text
# pass c to something here that starts selecting and moves
# the cursor based on c, and knows whether it's selecting
# via shift-arrow or mouse.
elif c == None: pass
elif c <= 128 and c >= 32: # printable ASCII character
self.insertChar(c, fg=self.colorfg, bg=self.colorbg)
sumbitch.append(chr(c)) # cheat codes
if len(sumbitch) >= sumbitch_len:
sumbitch.pop(0)
for heat in intense_burning:
if ''.join(sumbitch).endswith(heat):
self.activate_heat_code()
self.appState.renderMouseCursor = False
#else:
# self.notify(f"Weird character: {chr(c)} or {c}")
#self.drawStatusBar()
if self.appState.viewModeShowInfo:
self.showFileInformation()
self.refresh(refreshScreen=False)
#self.hardRefresh()
def selectColorPicker(self, message=None):
#if self.appState.colorMode == "256":
self.appState.colorPickerSelected = True
#self.statusBar.colorPicker.handler.showColorPicker()
self.statusBar.colorPicker.showFgPicker(message=message)
#if self.appState.colorMode == "16":
# self.statusBar.colorPicker_bg_16.showFgPicker()
self.appState.colorPickerSelected = False
def replaceColorUnderCursor(self):
self.commandMode = False
#self.notify("Pick a new color")
# Search and replace color under cursor
# Save old (UI) color setting, so we can use the color picker and then set the color back when done
ui_fg = self.colorfg
ui_bg = self.colorbg
#charColor = [self.colorfg, self.colorbg]
#old_color_pair =
# Get color pair under cursor
#old_fg, new_bg = self.mov.currentFrame.newColorMap[line][col]
old_fg, old_bg = self.mov.currentFrame.newColorMap[self.xy[0]][self.xy[1]-1]
oldCharColor = [old_fg, old_bg]
# Print a message for the user to set the New color
self.clearStatusLine()
printMessage = "Please pick the New color."
self.addstr(self.statusBarLineNum, 0, printMessage, curses.color_pair(self.appState.theme['notificationColor']))
self.stdscr.refresh()
# Use color picker to pick new destination color (pair)
#self.selectColorPicker(message=printMessage)
self.selectColorPicker()
self.clearStatusLine()
new_fg = self.colorfg
new_bg = self.colorbg
newCharColor = [new_fg, new_bg]
# Use movie search and replace color function
askingAboutRange = False
if self.mov.hasMultipleFrames():
self.promptPrint("Apply to all frames in playback range (Y/N)? ")
askingAboutRange = True
else: # only 1 frame in movie, so just apply to akk without asking
self.undo.push()
self.mov.search_and_replace_color_pair(oldCharColor, newCharColor)
askingAboutRange = False
while askingAboutRange:
prompt_ch = self.stdscr.getch()
if chr(prompt_ch) in ['y', 'Y']: # yes, all in range
self.undo.push()
self.mov.search_and_replace_color_pair(oldCharColor, newCharColor, frange=self.appState.playbackRange)
askingAboutRange = False
if chr(prompt_ch) in ['n', 'N']: # No, only current frame
self.undo.push()
self.mov.search_and_replace_color_pair(oldCharColor, newCharColor)
askingAboutRange = False
elif prompt_ch == 27: # esc, cancel
askingAboutRange = False
#self.notify(f"Replaced {oldCharColor} with {newCharColor}")
# Set UI color back to what it should be
self.setFgColor(ui_fg)
self.setBgColor(ui_bg)
self.stdscr.refresh()
def cloneToNewFrame(self):
""" Take current frame, clone it to a new one, insert it immediately after current frame """
self.undo.push()
if self.mov.insertCloneFrame():
if self.appState.playbackRange[0] == 1 and \
self.appState.playbackRange[1] == self.mov.frameCount - 1:
self.appState.playbackRange = (self.appState.playbackRange[0], \
self.appState.playbackRange[1] + 1)
self.refresh()
def appendEmptyFrame(self):
self.undo.push()
if self.mov.addEmptyFrame():
if self.appState.playbackRange[0] == 1 and \
self.appState.playbackRange[1] == self.mov.frameCount - 1:
self.appState.playbackRange = (self.appState.playbackRange[0], \
self.appState.playbackRange[1] + 1)
self.refresh()
def deleteCurrentFrame(self):
if self.deleteCurrentFramePrompt():
if self.appState.playbackRange[0] == 1 and \
self.appState.playbackRange[1] == self.mov.frameCount + 1:
self.appState.playbackRange = (self.appState.playbackRange[0], \
self.appState.playbackRange[1] - 1)
self.refresh()
def shiftMovieRight(self):
""" Shift all frames to the right, wrapping back around """
if self.mov.shift_right():
self.mov.nextFrame()
self.mov.prevFrame()
#self.notify("Shifted frames to the right.")
self.refresh()
def shiftMovieLeft(self):
""" Shift all frames to the left, wrapping back around """
if self.mov.shift_left():
self.mov.nextFrame()
self.mov.prevFrame()
#self.notify("Shifted frames to the right.")
self.refresh()
def enterViewMode(self, mov=None, opts=None):
self.statusBar.hide()
self.stdscr.clear()
old_xy = self.xy
old_top_line = self.appState.topLine
old_first_col = self.appState.firstCol
self.appState.topLine = 0
oldDrawBorders = self.appState.drawBorders # to turn back on when done
self.appState.playOnlyMode = True
self.startPlaying(mov=mov, opts=opts)
# Return to normal when done
self.appState.playOnlyMode = False
self.appState.topLine = old_top_line
self.appState.firstCol = old_first_col
self.xy = old_xy
self.statusBar.show()
self.appState.drawBorders = oldDrawBorders
self.cursorOn()
self.stdscr.clear()
def move_cursor_enter(self):
# move the cursor down
if self.xy[0] == self.mov.sizeY - 1:
self.addLineToCanvas()
if self.xy[0] < self.mov.sizeY:
self.move_cursor_down()
self.move_cursor_home()
self.appState.renderMouseCursor = False
def move_cursor_pgup(self):
# if we're at the top of the screen
if self.xy[0] == 0: # already on first line
if self.xy[1] > 0: # not on first column..
self.xy[1] = 1 # so go there
# top of screen, but not top of file, go up a page
elif self.xy[0] == self.appState.topLine:
pageSize = self.realmaxY - 2
self.xy[0] -= pageSize
self.appState.topLine -= pageSize
if self.xy[0] < 0: # if we overflowed into negatives...
self.xy[0] = 0
self.appState.topLine = 0
# not at top of screen at all, go to top of screen
elif self.xy[0] > self.appState.topLine:
self.xy[0] = self.appState.topLine
self.appState.renderMouseCursor = False
def move_cursor_pgdown(self):
bottomLine = self.realmaxY - 3 + self.appState.topLine
if bottomLine > self.mov.sizeY:
bottomLine = self.mov.sizeY - 1
# We're already on the last line
if self.xy[0] == self.mov.sizeY - 1:
if self.xy[1] > 0: # not on first column..
self.xy[1] = 1 # so go there
# bottom of screen, not bottom of file, go down a page
elif self.xy[0] == bottomLine:
pageSize = self.realmaxY - 2
self.xy[0] += pageSize
self.appState.topLine += pageSize
if self.xy[0] > self.mov.sizeY - 1: # if we overshot the end of the file..
self.xy[0] = self.mov.sizeY - 1
self.appState.topLine = self.mov.sizeY - pageSize
# middle of screen, go to bottom
elif self.xy[0] - self.appState.topLine < bottomLine:
self.xy[0] = bottomLine
if self.xy[1] < self.appState.firstCol + 1:# Scrolled off screen ot the left, Need to scroll left
self.appState.firstCol = self.xy[1] - 1
self.appState.renderMouseCursor = False
def move_cursor_topleft(self):
self.appState.topLine = 0
self.appState.firstCol = 0
self.xy[0] = 0
self.xy[1] = 1
self.refresh()
self.appState.renderMouseCursor = False
def move_cursor_left(self): # pressed LEFT key
if self.xy[1] > 1:
self.xy[1] = self.xy[1] - 1
if self.xy[1] < self.appState.firstCol + 1:# Scrolled off screen ot the left, Need to scroll left
self.appState.firstCol = self.xy[1] - 1
self.appState.renderMouseCursor = False
def move_cursor_right(self): # pressed RIGHT key
if self.xy[1] < self.mov.sizeX:
self.xy[1] = self.xy[1] + 1
#lastCol = min(mov.sizeX, self.appState.realmaxX)
if self.xy[1] - self.appState.firstCol > self.appState.realmaxX: # scrolled off screen to the right, need to scroll right
self.appState.firstCol = self.xy[1] - self.appState.realmaxX
self.appState.renderMouseCursor = False
def scroll_viewer_left(self): # pressed LEFT key in viewer mode
if self.appState.firstCol > 0:
self.appState.firstCol = self.appState.firstCol - 1
def scroll_viewer_right(self):
# if we are already scrolled right
last_column_shown = self.appState.firstCol + self.appState.realmaxX
if last_column_shown > self.mov.sizeX:
pass # do nothing
else: # otherwise, scroll right one
self.appState.firstCol += 1
def move_cursor_up(self): # pressed UP key
if self.xy[0] > 0:
self.xy[0] = self.xy[0] - 1
if self.xy[0] - self.appState.topLine + 1 == 0 and self.appState.topLine > 0: # if we're at the top of the screen
self.appState.topLine -= 1 # scrolll up a line
self.appState.renderMouseCursor = False
def move_cursor_down(self): # pressed DOWN key
if self.xy[0] < self.mov.sizeY - 1:
self.xy[0] = self.xy[0] + 1 # down a line
if self.xy[0] - self.appState.topLine > self.realmaxY - 3: # if we're at the bottom of the screen
self.appState.topLine += 1 # scroll down a line
self.appState.renderMouseCursor = False
def move_cursor_home(self):
self.xy[1] = 1
self.appState.firstCol = 0
if self.xy[1] < self.appState.firstCol + 1: # Scrolled off screen ot the left, Need to scroll left
self.appState.firstCol = self.xy[1] - 1
self.appState.renderMouseCursor = False
def move_cursor_end(self):
self.xy[1] = self.mov.sizeX
if self.xy[1] > self.appState.realmaxX: # scrolled off screen to the right, need to scroll right
self.appState.firstCol = self.xy[1] - self.appState.realmaxX
self.appState.renderMouseCursor = False
def move_cursor_to_line_and_column(self, line, col):
self.appState.topLine = line
self.xy[0] = line
self.xy[1] = col
self.appState.renderMouseCursor = False
def getDelayValue(self):
""" Ask the user for the delay value to set for current frame, then
set it """
self.clearStatusLine()
self.stdscr.nodelay(0) # wait for input when calling getch
self.promptPrint(f"Current frame delay == {self.mov.currentFrame.delay}, new value in seconds: ")
curses.echo()
try:
delayValue = float(self.stdscr.getstr())
except ValueError:
delayValue = -1
curses.noecho()
self.clearStatusLine()
if delayValue >= 0.0 and delayValue <= 120.0: # hard limit of 0 to 120 seconds
self.undo.push()
self.mov.currentFrame.setDelayValue(delayValue)
else:
self.notify("Delay must be between 0-120 seconds.")
def deleteCurrentFramePrompt(self):
self.clearStatusLine()
self.promptPrint("Are you sure you want to delete the current frame? (Y/N) ")
prompting = True
while prompting:
time.sleep(0.01)
c = self.stdscr.getch()
if c == 121: # 'y'
prompting = False
self.undo.push()
self.mov.deleteCurrentFrame()
self.clearStatusLine()
return True
elif c == 110: # 'n'
prompting = False
self.clearStatusLine()
return False
time.sleep(0.01)
def safeQuit(self):
self.stdscr.nodelay(0) # wait for input when calling getch
self.clearStatusLine()
if self.appState.modified:
self.promptPrint("Changes have not been saved! Are you sure you want to Quit? (Y/N) " )
else:
self.promptPrint("Are you sure you want to Quit? (Y/N) " )
prompting = True
while prompting:
time.sleep(0.01)
c = self.stdscr.getch()
if c == 121: # 121 = y
exiting = True
prompting = False
elif c == 110: # 110 = n
exiting = False
prompting = False
time.sleep(0.01)
self.clearStatusLine()
if exiting:
self.verySafeQuit()
def jumpToPythonConsole(self):
self.getReadyToSuspend()
pdb.set_trace()
self.resumeFromSuspend()
def getReadyToSuspend(self):
# Get the terminal ready for fun times
curses.nocbreak()
self.stdscr.keypad(0)
curses.echo()
def resumeFromSuspend(self):
# Get the terminal ready for fun times
curses.cbreak()
self.stdscr.keypad(1)
curses.noecho()
def verySafeQuit(self): # non-interactive part.. close out curses screen and exit.
curses.nocbreak()
self.stdscr.keypad(0)
curses.echo()
curses.endwin()
exit(0)
def promptPrint(self, promptText):
""" Prints prompting text in a consistent manner """
self.addstr(self.statusBarLineNum, 0, promptText, curses.color_pair(self.appState.theme['promptColor']))
def openTransformMenu(self):
""" Show the status bar's menu for settings """
#self.statusBar.mainMenu.handler.panel.show()
self.statusBar.animMenu.handler.panel.show()
#response = self.statusBar.transformMenu.showHide()
response = self.statusBar.transformMenu.show()
if response == "Pop":
pass
else:
self.statusBar.animMenu.handler.panel.hide()
def openSettingsMenu(self):
""" Show the status bar's menu for settings """
self.statusBar.mainMenu.handler.panel.show()
response = self.statusBar.settingsMenu.showHide()
self.statusBar.mainMenu.handler.panel.hide()
def openDrawCharPicker(self):
self.stdscr.nodelay(0)
#if self.appState.debug: self.notify(f"Loading character picker.")
self.statusBar.drawCharPicker.pickChar()
#if self.appState.debug: self.notify(f"Closing character picker.")
#self.statusBar.toolMenu.handler.panel.hide()
#self.statusBar.mainMenu.showHide()
if self.playing:
self.stdscr.nodelay(1)
def openMainMenu(self):
self.openMenu("File")
def openAnimMenu(self):
self.openMenu("Anim")
def openMouseToolsMenu(self):
self.openMenu("Mouse Tools")
def setCursorModePaint(self):
if self.appState.brush == None:
self.notify("No brush set. Please use select tool to make one before using Paint.", pause=True)
else:
self.statusBar.setCursorModePaint()
def openMenu(self, current_menu: str):
menu_open = True
self.stdscr.nodelay(0) # wait for input when calling getch
cmode = self.appState.cursorMode
if cmode == "Draw" or cmode == "Paint":
self.disableMouseReporting()
if not self.statusBar.toolButton.hidden:
self.drawStatusBar()
response = "Right"
if self.playing:
menus = ["File"]
else:
menus = ["File", "Anim", "Mouse Tools"]
#fail_count = 0 # debug
while menu_open:
if current_menu == "File":
#response = self.statusBar.menuButton.on_click()
self.statusBar.menuButton.become_selected()
response = self.statusBar.mainMenu.showHide()
self.statusBar.menuButton.draw() # redraw as unselected/not-inverse
elif current_menu == "Mouse Tools":
#response = self.statusBar.toolButton.on_click()
self.statusBar.toolButton.become_selected()
response = self.statusBar.toolMenu.showHide()
self.statusBar.toolButton.draw() # redraw as unselected/not-inverse
elif current_menu == "Anim":
#response = self.statusBar.toolButton.on_click()
self.statusBar.animButton.become_selected()
response = self.statusBar.animMenu.showHide()
self.statusBar.animButton.draw() # redraw as unselected/not-inverse
if response == "Close":
menu_open = False
if cmode == "Draw" or cmode == "Paint":
self.enableMouseReporting()
self.hardRefresh()
elif response == "Right":
# if we're at the rightmost menu
if menus.index(current_menu) == len(menus) - 1:
current_menu = menus[0] # circle back around
else:
next_menu_index = menus.index(current_menu) + 1
current_menu = menus[next_menu_index]
elif response == "Left":
# If we're at the leftmose menu
if menus.index(current_menu) == 0:
next_menu_index = len(menus) - 1
current_menu = menus[next_menu_index]
else:
next_menu_index = menus.index(current_menu) - 1
current_menu = menus[next_menu_index]
#fail_count += 1 # debug
#if fail_count > 3:
# pdb.set_trace()
if cmode == "Draw" or cmode == "Paint":
self.enableMouseReporting()
#self.hardRefresh()
if self.playing:
self.stdscr.nodelay(1)
def openFromMenu(self):
#self.stdscr.nodelay(0) # wait for input when calling getch
self.clearStatusLine()
if self.appState.modified:
self.promptPrint("Changes have not been saved! Are you sure you want to load another file? (Y/N) ")
prompting = True
while prompting:
time.sleep(0.01)
c = self.stdscr.getch()
if c == 121: # 121 = y
prompting = False
elif c == 110 or c == 27: # 110 == n, 27 == esc
prompting = False
return None
time.sleep(0.01)
self.clearStatusLine()
load_filename = self.openFilePicker()
if load_filename: # if not False
self.clearCanvas(prompting=False)
self.loadFromFile(load_filename, 'dur')
self.move_cursor_topleft()
self.stdscr.clear()
self.hardRefresh()
def toggleColorScrolling(self):
self.appState.scrollColors = not self.appState.scrollColors
def toggleSideBar(self):
if self.appState.sideBarEnabled:
self.appState.sideBarEnabled = False
self.statusBar.colorPicker.hide()
if self.appState.colorMode == "16":
self.statusBar.colorPicker_bg_16.hide()
else:
self.appState.sideBarEnabled = True
#if self.appState.colorMode == "256":
self.statusBar.colorPicker.show()
if self.appState.colorMode == "16":
self.statusBar.colorPicker_bg_16.show()
def showCharSetPicker(self):
set_list = ["Durdraw Default"]
block_list = self.appState.unicodeBlockList.copy()
for set_name in set_list:
block_list.insert(0, set_name)
# draw ui
selected_item_number = 0
current_line_number = 0
search_string = ""
mask_all = False
top_line = 0 # topmost viewable line, for scrolling
prompting = True
# Turn on keyboard buffer waiting here, if necessary..
self.stdscr.nodelay(0)
self.stdscr.clear()
while prompting:
# draw list of files from top of the window to bottomk
realmaxY,realmaxX = self.realstdscr.getmaxyx()
page_size = realmaxY - 4
current_line_number = 0
if selected_item_number > top_line + page_size-1 or selected_item_number < top_line: # item is off the screen
top_line = selected_item_number - int(page_size-3) # scroll so it's at the bottom
for blockname in block_list:
if current_line_number >= top_line and current_line_number - top_line < page_size: # If we're within screen size
currentActiveSet = False
if blockname == self.appState.characterSet: # currently used character set
currentActiveSet = True
block_label = f"{block_list[current_line_number]} *"
elif self.appState.characterSet == "Unicode Block" and blockname == self.appState.unicodeBlock:
currentActiveSet = True
block_label = f"{block_list[current_line_number]} *"
else:
block_label = block_list[current_line_number]
if selected_item_number == current_line_number: # if block is selected
self.addstr(current_line_number - top_line, 0, block_label, curses.A_REVERSE)
if block_list[current_line_number] not in set_list: # if it's a unicode block...
# Draw inline preview characters for the set
#previewCharMap = durchar.load_unicode_block(block_list[selected_item_number])
previewCharMap = durchar.load_unicode_block(block_list[current_line_number])
previewChars = ''
maxChars = 60 # number of preview characters to load
totalChars = 0
previewOffset = len(block_label) + 2 # column to display preview characters at
for miniMap in previewCharMap: # for all characters in this block...
for key in miniMap:
if totalChars <= maxChars: # If we're within range,
previewChars += chr(miniMap[key]) # add to the preview string
totalChars += 1
try:
pass
self.addstr(current_line_number - top_line, previewOffset, previewChars)
except Exception as E:
pass
else: # print a block that isn't currently selected
if block_list[current_line_number] in set_list:
# Durdraw custom character set (like durdraw default), not a Unicode block
if currentActiveSet: # currently used character set
self.addstr(current_line_number - top_line, 0, block_label, curses.color_pair(self.appState.theme['menuTitleColor']) | curses.A_BOLD)
else:
self.addstr(current_line_number - top_line, 0, block_label, curses.color_pair(self.appState.theme['menuTitleColor']))
else:
if currentActiveSet:
self.addstr(current_line_number - top_line, 0, block_label, curses.color_pair(self.appState.theme['promptColor']) | curses.A_BOLD)
else:
self.addstr(current_line_number - top_line, 0, block_label, curses.color_pair(self.appState.theme['promptColor']))
# Draw inline preview characters for the set
#previewCharMap = durchar.load_unicode_block(block_list[selected_item_number])
previewCharMap = durchar.load_unicode_block(block_list[current_line_number])
previewChars = ''
maxChars = 60 # number of preview characters to load
totalChars = 0
previewOffset = len(block_label) + 2 # column to display preview characters at
for miniMap in previewCharMap: # for all characters in this block...
for key in miniMap:
if totalChars <= maxChars: # If we're within range,
previewChars += chr(miniMap[key]) # add to the preview string
totalChars += 1
try:
pass
self.addstr(current_line_number - top_line, previewOffset, previewChars)
except Exception as E:
pass
current_line_number += 1
#if mask_all:
# self.addstr(realmaxY - 4, 0, f"[X]", curses.color_pair(self.appState.theme['clickColor']))
#else:
# self.addstr(realmaxY - 4, 0, f"[ ]", curses.color_pair(self.appState.theme['clickColor']))
#self.addstr(realmaxY - 4, 4, f"Show All Files", curses.color_pair(self.appState.theme['menuItemColor']))
#self.addstr(realmaxY - 4, 20, f"[PGUP]", curses.color_pair(self.appState.theme['clickColor']))
#self.addstr(realmaxY - 4, 27, f"[PGDOWN]", curses.color_pair(self.appState.theme['clickColor']))
#self.addstr(realmaxY - 4, 36, f"[OK]", curses.color_pair(self.appState.theme['clickColor']))
#self.addstr(realmaxY - 4, 41, f"[CANCEL]", curses.color_pair(self.appState.theme['clickColor']))
#self.addstr(realmaxY - 3, 0, f"Folder: {current_directory}", curses.color_pair(self.appState.theme['menuTitleColor']))
if search_string != "":
self.addstr(realmaxY - 2, 0, f"search: ")
self.addstr(realmaxY - 2, 8, f"{search_string}", curses.color_pair(self.appState.theme['menuItemColor']))
# print preview characters
errorLoadingBlock = False
if block_list[selected_item_number] in set_list: # not a unicode block
errorLoadingBlock = False
self.addstr(realmaxY - 1, 0, f"Character set: {block_list[selected_item_number]}")
else:
previewCharMap = durchar.load_unicode_block(block_list[selected_item_number])
self.addstr(realmaxY - 1, 0, f"Unicode block: {block_list[selected_item_number]}")
previewChars = "Preview: "
maxChars = 100
totalChars = 0
for miniMap in previewCharMap: # for all characters in this block...
for key in miniMap:
if totalChars <= maxChars: # If we're within range,
previewChars += chr(miniMap[key]) # add to the preview string
totalChars += 1
try:
self.addstr(realmaxY - 4, 0, previewChars)
errorLoadingBlock = False
except Exception as E:
errorLoadingBlock = True
self.addstr(realmaxY - 4, 0, "Cannot load this block")
self.stdscr.refresh()
c = self.stdscr.getch()
self.stdscr.clear()
if c == curses.KEY_LEFT:
pass
elif c == curses.KEY_RIGHT:
pass
elif c == curses.KEY_UP:
# move cursor up
if selected_item_number > 0:
if selected_item_number == top_line and top_line != 0:
top_line -= 1
selected_item_number -= 1
pass
elif c == curses.KEY_DOWN:
if selected_item_number < len(block_list) - 1:
# move cursor down
selected_item_number += 1
# if we're at the bottom of the screen...
if selected_item_number - top_line == page_size and top_line < len(block_list) - page_size:
top_line += 1
elif c in [339, curses.KEY_PPAGE]: # page up
if selected_item_number - top_line > 0: # first go to the top of the page
selected_item_number = top_line
else: # if already there, go up a full page
selected_item_number -= page_size
top_line -= page_size
# correct any overflow
if selected_item_number < 0:
selected_item_number = 0
if top_line < 0:
top_line = 0
elif c in [338, curses.KEY_NPAGE]: # page down
if selected_item_number - top_line < page_size - 1: # first go to bottom of the page
selected_item_number = page_size + top_line - 1
else: # if already there, go down afull page
selected_item_number += page_size
top_line += page_size
# correct any overflow
if selected_item_number >= len(block_list):
selected_item_number = len(block_list) - 1
if top_line >= len(block_list):
top_line = len(block_list) - page_size
elif c in [339, curses.KEY_HOME]: # 339 = home
selected_item_number = 0
top_line = 0
elif c in [338, curses.KEY_END]: # 338 = end
selected_item_number = len(block_list) - 1
top_line = selected_item_number - page_size + 1
if top_line < 0: # for small file lists
top_line = 0
elif c in [13, curses.KEY_ENTER]:
if errorLoadingBlock:
pass
elif block_list[selected_item_number] in set_list:
# Durdraw character set selected. Set it
self.setCharacterSet(block_list[selected_item_number])
#self.appState.characterSet = block_list[selected_item_number]
self.charMapNumber = 0
self.initCharSet()
self.stdscr.clear()
prompting = False
else:
# Unicode block selected.
self.appState.characterSet = "Unicode Block"
self.charMapNumber = 0
self.appState.unicodeBlock = block_list[selected_item_number]
self.setUnicodeBlock(block=self.appState.unicodeBlock)
self.stdscr.clear()
prompting = False
#pdb.set_trace()
#full_path = f"{current_directory}/{block_list[selected_item_number]}"
#return full_path
elif c == 27: # esc key
if search_string != "":
search_string = ""
else:
self.stdscr.clear()
prompting = False
if self.playing:
self.stdscr.nodelay(1)
return False
elif c in [' curses.KEY_BACKSPACE', 263, 127]: # backspace
if search_string != "":
# if string is not empty, remove last character
search_string = search_string[:len(search_string)-1]
elif c == curses.KEY_MOUSE:
try:
_, mouseCol, mouseLine, _, mouseState = curses.getmouse()
except:
pass
if mouseState == curses.BUTTON1_CLICKED or mouseState == curses.BUTTON1_DOUBLE_CLICKED:
self.pressingButton = False
if self.pushingToClip:
self.pushingToClip = False
if self.appState.cursorMode != "Draw" and self.appState.cursorMode != "Paint":
print('\033[?1003l') # disable mouse reporting
self.hardRefresh()
curses.mousemask(1)
curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
if mouseLine < realmaxY - 4: # above the 'status bar,' in the file list
if mouseLine < len(block_list) - top_line: # clicked item line
if mouseCol < len(block_list[top_line+mouseLine]): # clicked within item width
selected_item_number = top_line+mouseLine
if mouseState == curses.BUTTON1_DOUBLE_CLICKED:
if block_list[selected_item_number] in set_list: # clicked set, not block
# holy fuck this is deep
self.appState.characterSet = block_list[selected_item_number]
self.charMapNumber = 0
self.initCharSet()
self.stdscr.clear()
prompting = False
else: # clicked a unicode block
self.appState.characterSet = "Unicode Block"
self.charMapNumber = 0
self.setUnicodeBlock(block=block_list[selected_item_number])
self.stdscr.clear()
prompting = False
#if mouseLine == realmaxY - 4: # on the button bar
# if mouseCol in range(0,3): # clicked [X] All
# if mask_all:
# mask_all = False
# masks = default_masks
# else:
# mask_all = True
# masks = ['*.*']
# update file list
if not self.appState.hasMouseScroll:
curses.BUTTON5_PRESSED = 0
curses.BUTTON4_PRESSED = 0
try:
curses.BUTTON5_PRESSED
except:
curses.BUTTON5_PRESSED = 0
try:
curses.BUTTON4_PRESSED
except:
curses.BUTTON4_PRESSED = 0
if mouseState & curses.BUTTON4_PRESSED: # wheel up
# scroll up
# if the item isn't at the top of teh screen, move it up
if selected_item_number > top_line:
selected_item_number -= 1
elif top_line > 0:
top_line -= 1
elif mouseState & curses.BUTTON5_PRESSED: # wheel down
# scroll down
if selected_item_number < len(block_list) - 1:
selected_item_number += 1
if selected_item_number == len(block_list) - top_line:
top_line += 1
else: # add to search string
search_string += chr(c)
search_string = search_string.lower() # case insensitive search
for blockname in block_list: # search list for search_string
# prioritize unicode block names for some reason
if blockname not in set_list and blockname.lower().startswith(search_string):
selected_item_number = block_list.index(blockname)
break # stop at the first match
# then search character sets
elif blockname in set_list and blockname.lower().startswith(search_string):
selected_item_number = block_list.index(blockname)
break # stop at the first match
# Finally if nothing begins with the search string, see if
# any items contain the search string
elif search_string in blockname.lower():
selected_item_number = block_list.index(blockname)
break # stop at the first match
def openFilePicker(self):
""" Draw UI for selecting a file to load, return the filename """
# get file list
folders = ["../"]
default_masks = ['*.dur', '*.durf', '*.asc', '*.ans', '*.txt', '*.diz', '*.nfo', '*.ice', '*.ansi']
masks = default_masks
if self.appState.workingLoadDirectory:
if os.path.exists(self.appState.workingLoadDirectory):
current_directory = self.appState.workingLoadDirectory
else:
current_directory = os.getcwd()
else:
current_directory = os.getcwd()
#folders += sorted(glob.glob(f"{current_directory}/*/"))
#folders += sorted(glob.glob("*/", root_dir=current_directory)) # python 3.10+
# python 3.9 compatible block instead:
folders += sorted(filter(os.path.isdir, glob.glob(os.path.join(current_directory, "*/"))))
# remove leading paths
new_folders = []
for path_string in folders:
new_folders.append(os.path.sep.join(path_string.split(os.path.sep)[-2:]))
folders = new_folders
matched_files = []
file_list = []
for file in os.listdir(current_directory):
for mask in masks:
if fnmatch.fnmatch(file.lower(), mask.lower()):
matched_files.append(file)
break
for dirname in folders:
file_list.append(dirname)
file_list += sorted(matched_files)
# stash away file list so we can use it for search, and pop it back
# in when user hits esc
full_file_list = file_list
# draw ui
selected_item_number = 0
current_line_number = 0
search_string = ''
mask_all = False
tabbed = False
show_modtime = True # show a column for file modified times
top_line = 0 # topmost viewable line, for scrolling
prompting = True
# Turn on keyboard buffer waiting here, if necessary..
self.stdscr.nodelay(0)
self.stdscr.clear()
while prompting:
# Set search matching
filtered_list = [item for item in full_file_list if search_string.lower() in item.lower()]
if search_string != '':
file_list = [item for item in full_file_list if search_string.lower() in item.lower()]
if len(file_list) == 0:
file_list = ["../"]
else:
file_list = full_file_list
# draw list of files from top of the window to bottom
realmaxY,realmaxX = self.realstdscr.getmaxyx()
page_size = realmaxY - 4
current_line_number = 0
file_column_number = 0
if show_modtime:
file_column_number = 20
if selected_item_number > top_line + page_size-1 or selected_item_number < top_line: # item is off the screen
top_line = selected_item_number - int(page_size-3) # scroll so it's at the bottom
for filename in file_list:
if current_line_number >= top_line and current_line_number - top_line < page_size: # If we're within screen size
if selected_item_number == current_line_number and not tabbed: # if file is selected
if file_list[current_line_number] in folders:
self.addstr(current_line_number - top_line, 0, file_list[current_line_number], curses.A_REVERSE)
else:
# not a folder, print modified column
if show_modtime:
full_path = f"{current_directory}/{file_list[current_line_number]}"
file_modtime_string = durfile.get_file_mod_date_time(full_path)
self.addstr(current_line_number - top_line, 0, file_modtime_string, curses.color_pair(self.appState.theme['menuItemColor']))
self.addstr(current_line_number - top_line, file_column_number, file_list[current_line_number], curses.A_REVERSE)
else:
if file_list[current_line_number] in folders:
self.addstr(current_line_number - top_line, 0, file_list[current_line_number], curses.color_pair(self.appState.theme['menuTitleColor']))
else:
if show_modtime:
full_path = f"{current_directory}/{file_list[current_line_number]}"
file_modtime_string = durfile.get_file_mod_date_time(full_path)
self.addstr(current_line_number - top_line, 0, file_modtime_string, curses.color_pair(self.appState.theme['menuItemColor']))
self.addstr(current_line_number - top_line, file_column_number, file_list[current_line_number], curses.color_pair(self.appState.theme['promptColor']))
current_line_number += 1
if mask_all:
if tabbed:
self.addstr(realmaxY - 4, 0, f"[X]", curses.A_REVERSE)
else:
self.addstr(realmaxY - 4, 0, f"[X]", curses.color_pair(self.appState.theme['clickColor']))
else:
if tabbed:
self.addstr(realmaxY - 4, 0, f"[ ]", curses.A_REVERSE)
else:
self.addstr(realmaxY - 4, 0, f"[ ]", curses.color_pair(self.appState.theme['clickColor']))
self.addstr(realmaxY - 4, 4, f"Show All Files", curses.color_pair(self.appState.theme['menuItemColor']))
#self.addstr(realmaxY - 4, 20, f"[PGUP]", curses.color_pair(self.appState.theme['clickColor']))
#self.addstr(realmaxY - 4, 27, f"[PGDOWN]", curses.color_pair(self.appState.theme['clickColor']))
#self.addstr(realmaxY - 4, 36, f"[OK]", curses.color_pair(self.appState.theme['clickColor']))
#self.addstr(realmaxY - 4, 41, f"[CANCEL]", curses.color_pair(self.appState.theme['clickColor']))
self.addstr(realmaxY - 3, 0, f"Folder: {current_directory}", curses.color_pair(self.appState.theme['menuTitleColor']))
if search_string != "":
self.addstr(realmaxY - 2, 0, f"search: ")
self.addstr(realmaxY - 2, 8, f"{search_string}", curses.color_pair(self.appState.theme['menuItemColor']))
if selected_item_number > len(file_list) - 1:
selected_item_number = 0
filename = file_list[selected_item_number]
full_path = f"{current_directory}/{file_list[selected_item_number]}"
if filename not in folders:
# read sauce, if available
#file_sauce = dursauce.SauceParser(full_path)
file_sauce = dursauce.SauceParser()
file_sauce.parse_file(full_path)
sauce_title = file_sauce.title
sauce_author = file_sauce.author
sauce_width = file_sauce.width
sauce_height = file_sauce.height
sauce_date = file_sauce.date
sauce_year = file_sauce.year
sauce_month = file_sauce.month
sauce_day = file_sauce.day
sauce_width = file_sauce.width
sauce_height = file_sauce.height
else:
file_sauce = dursauce.SauceParser() # empty placeholder sauce
sauce_title = None
sauce_author = None
#sauce_width = file_sauce.width
#sauce_height = file_sauce.height
file_size = os.path.getsize(full_path)
file_modtime_string = durfile.get_file_mod_date_time(full_path)
# display file info - format data
file_info = f"File: {filename}, Size: {file_size}, Modified: {file_modtime_string}"
if file_sauce.sauce_found:
file_info = f"{sauce_title}, Artist: {sauce_author}, Date: {sauce_year}/{sauce_month}/{sauce_day}, Width: {sauce_width}, Height: {sauce_height}, Size: {file_size}"
# show it on screen
self.addstr(realmaxY - 1, 0, f"{file_info}")
#self.stdscr.refresh()
# Read keyboard input
c = self.stdscr.getch()
if tabbed:
# Change focus from files to other elements, ie: Show All Files checkbox
#c = self.stdscr.getch()
if c in [9, curses.KEY_UP, curses.KEY_DOWN]: # 9 = Tab
# Change focus away, back to file lister
tabbed = False
elif c in [ord(' '), 13, curses.KEY_ENTER]: # 13 = enter
# Check or uncheck Show All Files
mask_all = not mask_all
if mask_all:
masks = ['*.*']
else:
masks = default_masks
# update file list
matched_files = []
file_list = []
for file in os.listdir(current_directory):
for mask in masks:
if fnmatch.fnmatch(file.lower(), mask.lower()):
matched_files.append(file)
break
for dirname in folders:
file_list.append(dirname)
file_list += sorted(matched_files)
# reset ui
selected_item_number = 0
search_string = ""
full_file_list = file_list
elif c in [27]: # esc
tabbed = False
prompting = False
c = None
self.stdscr.clear()
if c == curses.KEY_LEFT:
pass
elif c == curses.KEY_RIGHT:
pass
elif c in [9]: # 9 == tab key
tabbed = not tabbed
elif c == curses.KEY_UP:
# move cursor up
if selected_item_number > 0:
if selected_item_number == top_line and top_line != 0:
top_line -= 1
selected_item_number -= 1
pass
elif c == curses.KEY_DOWN:
if selected_item_number < len(file_list) - 1:
# move cursor down
selected_item_number += 1
# if we're at the bottom of the screen...
if selected_item_number - top_line == page_size and top_line < len(file_list) - page_size:
top_line += 1
elif c in [339, curses.KEY_PPAGE]: # page up
if selected_item_number - top_line > 0: # first go to the top of the page
selected_item_number = top_line
else: # if already there, go up a full page
selected_item_number -= page_size
top_line -= page_size
# correct any overflow
if selected_item_number < 0:
selected_item_number = 0
if top_line < 0:
top_line = 0
elif c in [338, curses.KEY_NPAGE]: # page down
if selected_item_number - top_line < page_size - 1: # first go to bottom of the page
selected_item_number = page_size + top_line - 1
else: # if already there, go down afull page
selected_item_number += page_size
top_line += page_size
# correct any overflow
if selected_item_number >= len(file_list):
selected_item_number = len(file_list) - 1
if top_line >= len(file_list):
top_line = len(file_list) - page_size
elif c in [339, curses.KEY_HOME]: # 339 = home
selected_item_number = 0
top_line = 0
elif c in [338, curses.KEY_END]: # 338 = end
selected_item_number = len(file_list) - 1
top_line = selected_item_number - page_size + 1
if top_line < 0: # for small file lists
top_line = 0
elif c in [13, curses.KEY_ENTER]:
if file_list[selected_item_number] in folders:
# change directories
if file_list[selected_item_number] == "../":
# "cd .."
current_directory = os.path.split(current_directory)[0]
else:
current_directory = f"{current_directory}/{file_list[selected_item_number]}"
if current_directory[-1] == "/":
current_directory = current_directory[:-1]
# get file list
folders = ["../"]
#folders += sorted(glob.glob("*/", root_dir=current_directory))
folders += sorted(filter(os.path.isdir, glob.glob(os.path.join(current_directory, "*/"))))
# remove leading paths
new_folders = []
for path_string in folders:
new_folders.append(os.path.sep.join(path_string.split(os.path.sep)[-2:]))
folders = new_folders
if mask_all:
masks = ['*.*']
else:
masks = default_masks
matched_files = []
file_list = []
for file in os.listdir(current_directory):
for mask in masks:
if fnmatch.fnmatch(file.lower(), mask.lower()):
matched_files.append(file)
break
for dirname in folders:
file_list.append(dirname)
file_list += sorted(matched_files)
# reset ui
top_line = 0
selected_item_number = 0
search_string = ""
full_file_list = file_list
else:
# return the selected file
self.stdscr.clear()
prompting = False
full_path = f"{current_directory}/{file_list[selected_item_number]}"
self.appState.workingLoadDirectory = current_directory
return full_path
elif c == 9: # 9 == tab
pass
#self.filePickerOptionsPicker()
elif c == 21: # ^U or ctrl-u
# clear the search field
if search_string != "":
search_string = ""
elif c == 27: # esc key
if search_string != "":
search_string = ""
else:
self.stdscr.clear()
prompting = False
if self.playing:
self.stdscr.nodelay(1)
return False
elif c in [' curses.KEY_BACKSPACE', 263, 127]: # backspace
if search_string != "":
# if string is not empty, remove last character
search_string = search_string[:len(search_string)-1]
elif c == curses.KEY_MOUSE:
try:
_, mouseCol, mouseLine, _, mouseState = curses.getmouse()
except:
pass
if mouseState == curses.BUTTON1_CLICKED or mouseState == curses.BUTTON1_DOUBLE_CLICKED:
if mouseLine < realmaxY - 4: # above the 'status bar,' in the file list
if mouseLine < len(file_list) - top_line: # clicked item line
if mouseCol < len(file_list[top_line+mouseLine] + file_modtime_string): # clicked within item width
selected_item_number = top_line+mouseLine
if mouseState == curses.BUTTON1_DOUBLE_CLICKED:
if file_list[selected_item_number] in folders: # clicked directory
# change directories. holy fuck this is deep
if file_list[selected_item_number] == "../":
# "cd .."
current_directory = os.path.split(current_directory)[0]
else:
current_directory = f"{current_directory}/{file_list[selected_item_number]}"
if current_directory[-1] == "/":
current_directory = current_directory[:-1]
# get file list
folders = ["../"]
#folders += glob.glob("*/", root_dir=current_directory)
folders += sorted(filter(os.path.isdir, glob.glob(os.path.join(current_directory, "*/"))))
# remove leading paths
new_folders = []
for path_string in folders:
new_folders.append(os.path.sep.join(path_string.split(os.path.sep)[-2:]))
folders = new_folders
if mask_all:
masks = ['*.*']
else:
masks = default_masks
matched_files = []
file_list = []
for file in os.listdir(current_directory):
for mask in masks:
if fnmatch.fnmatch(file.lower(), mask.lower()):
matched_files.append(file)
break
for dirname in folders:
file_list.append(dirname)
file_list += sorted(matched_files)
# reset ui
selected_item_number = 0
search_string = ""
else: # clicked a file, try to load it
full_path = f"{current_directory}/{file_list[selected_item_number]}"
return full_path
if mouseLine == realmaxY - 4: # on the button bar
if mouseCol in range(0,3): # clicked [X] All
if mask_all:
mask_all = False
masks = default_masks
else:
mask_all = True
masks = ['*.*']
# update file list
matched_files = []
file_list = []
for file in os.listdir(current_directory):
for mask in masks:
if fnmatch.fnmatch(file.lower(), mask.lower()):
matched_files.append(file)
break
for dirname in folders:
file_list.append(dirname)
file_list += sorted(matched_files)
# reset ui
selected_item_number = 0
search_string = ""
full_file_list = file_list
if not self.appState.hasMouseScroll:
curses.BUTTON5_PRESSED = 0
curses.BUTTON4_PRESSED = 0
try:
curses.BUTTON5_PRESSED
except:
curses.BUTTON5_PRESSED = 0
try:
curses.BUTTON4_PRESSED
except:
curses.BUTTON4_PRESSED = 0
if mouseState & curses.BUTTON4_PRESSED: # wheel up
# scroll up
# if the item isn't at the top of teh screen, move it up
if selected_item_number > top_line:
selected_item_number -= 1
elif top_line > 0:
top_line -= 1
elif mouseState & curses.BUTTON5_PRESSED: # wheel down
# scroll down
if selected_item_number < len(file_list) - 1:
selected_item_number += 1
if selected_item_number == len(file_list) - top_line:
top_line += 1
elif c == None:
pass
else: # add to search string
search_string += chr(c)
selected_item_number = 0
current_line_number = 0
top_line = 0
for filename in file_list: # search list for search_string
if filename not in folders and filename.startswith(search_string):
#selected_item_number = file_list.index(filename)
break # stop at the first match
def open(self):
self.clearStatusLine()
if self.appState.modified:
self.promptPrint("Changes have not been saved! Are you sure you want to load another file? (Y/N) ")
prompting = True
while prompting:
time.sleep(0.01)
c = self.stdscr.getch()
if c == 121: # 121 = y
prompting = False
elif c == 110 or c == 27: # 110 == n, 27 == esc
prompting = False
return None
time.sleep(0.01)
self.clearStatusLine()
self.move(self.mov.sizeY, 0)
self.stdscr.nodelay(0) # wait for input when calling getch
self.promptPrint("File format? [I] ASCII, [D] DUR (or JSON), [ESC] Cancel: ")
prompting = True
while prompting:
c = self.stdscr.getch()
time.sleep(0.01)
if c == 105: # 105 i = ascii
loadFormat = 'ascii'
prompting = False
elif c == 100: # 100 = d = dur
loadFormat = 'dur'
prompting = False
elif c == 27: # 27 = esc = cancel
self.clearStatusLine()
prompting = False
return None
self.clearStatusLine()
self.promptPrint("Enter file name to open: ")
curses.echo()
shortfile = self.stdscr.getstr().decode('utf-8')
curses.noecho()
if shortfile.replace(' ', '') == '':
self.notify("File name cannot be empty.")
return False
self.clearStatusLine()
if not self.loadFromFile(shortfile, loadFormat):
return False
self.undo = UndoManager(self, appState = self.appState) # reset undo system
self.stdscr.redrawwin()
self.stdscr.clear()
#self.refresh() # so we can see the new ascii in memory.
self.hardRefresh() # so we can see the new ascii in memory.
def convertToCurrentFormat(self, fileColorMode = None): # should this and loadFromFile be in a
# separate class for file operations? or into Movie() or Options() ?
# then loadFromFile could return a movie object instead.
""" If we load old .dur files, convert to the latest format """
# aka "fill in the blanks"
#if self.appState.debug: self.notify(f"Converting to new format. Current format: {self.opts.saveFileFormat}")
if self.opts.saveFileFormat < 3: # version 4 should rename saveFileFormat
if self.appState.debug: self.notify(f"Upgrading to format 3. Making old color map.")
# to saveFormatVersion
# initialize color map for all frames:
for frame in self.mov.frames:
frame.initColorMap()
self.opts.saveFileFormat = 3
if self.opts.saveFileFormat < 4:
if self.appState.debug: self.notify(f"Upgrading to format 4. Adding delays.")
# old file, needs delay times populated.
for frame in self.mov.frames:
frame.setDelayValue(0)
self.opts.saveFileFormat = 4
if self.opts.saveFileFormat < 5:
if self.appState.debug: self.notify(f"Upgrading to format 5. Making new color map.")
for frame in self.mov.frames:
frame.newColorMap = durmovie.convert_dict_colorMap(frame.colorMap, frame.sizeX, frame.sizeY)
self.opts.saveFileFormat = 5
if self.opts.saveFileFormat < 6:
if self.appState.debug: self.notify(f"Upgrading to format 6. Making new color map.")
for frame in self.mov.frames:
try:
frame.height # for really old pickle files
except:
frame.height = frame.sizeY
frame.width = frame.sizeY
for line in range(0, frame.height):
for col in range(0, frame.width):
oldPair = frame.newColorMap[line][col]
oldMode = self.appState.colorMode
self.opts.saveFileFormat = 6
convertedColorMap = False
if self.opts.saveFileFormat < 7:
if self.appState.debug: self.notify(f"Upgrading to format 7. Converting color map.")
if self.mov.contains_high_colors():
if self.appState.debug: self.notify(f"Old file format was 256 color.")
self.ansi.convert_colormap(self.mov, dur_ansilib.legacy_256_to_256)
else:
if self.appState.colorMode == '16':
if self.appState.debug: self.notify(f"Old file format was 16 color. Converting to new 16.")
self.ansi.convert_colormap(self.mov, dur_ansilib.legacy_16_to_16)
convertedColorMap = True
else:
if self.appState.debug: self.notify(f"Old file format was 16 color. Converting to new 256.")
self.ansi.convert_colormap(self.mov, dur_ansilib.legacy_16_to_256)
convertedColorMap = True
self.opts.saveFileFormat = 7
if fileColorMode == "16" and self.appState.colorMode == "256" and convertedColorMap == False:
for frame in self.mov.frames: # conert from 16 to 256 pallette
for line in range(0, frame.height):
for col in range(0, frame.width):
if frame.newColorMap[line][col][0] == 1: # convert black color
frame.newColorMap[line][col][0] = 16
frame.newColorMap[line][col][0] = frame.newColorMap[line][col][0] - 1 # convert rest of colors
if frame.newColorMap[line][col][1] == 1: # convert black color
frame.newColorMap[line][col][1] = 16
frame.newColorMap[line][col][1] = frame.newColorMap[line][col][1] - 1 # convert rest of colors
self.opts.saveFileFormat = self.appState.durFileVer
def loadFromFile(self, shortfile, loadFormat): # shortfile = non full path filename
filename = os.path.expanduser(shortfile)
if loadFormat == 'ascii': # or ANSI...
try:
if self.appState.debug2: self.notify("Trying to open() file as ascii.")
f = open(filename, 'r')
self.appState.curOpenFileName = os.path.basename(filename)
shortpath = os.path.split(filename)[0]
if len(os.path.split(shortpath)) > 1:
shortpath = os.path.split(shortpath)[1]
self.appState.fileShortPath = shortpath
#self.appState.fileLongPath = fullpath
except Exception as e:
#if self.appState.debug: self.notify(f"self.opts = pickle.load(f)")
self.notify(f"Could not open file for reading: {e}")
return None
# here we add the stuff to load the file into self.mov.currentFrame.content[][]
self.undo.push()
if self.appState.colorMode == '256':
fg, bg = 7, 0
elif self.appState.colorMode == '16':
fg, bg = 8, 0
lineNum = 0
colNum = 0
i = 0
try:
raw_text = f.read()
except UnicodeDecodeError:
f.close()
if self.appState.charEncoding == 'utf-8':
if not self.appState.playOnlyMode and self.appState.debug:
self.notify("This appears to be a CP437 ANSI/ASCII - Converting to Unicode.")
f = open(filename, 'r', encoding='cp437')
raw_text = f.read()
# Load file into a new frame, make a new movie,
#default_width= 80 # default with for ANSI file
if filename[-4].lower() == ".diz" or filename.lower().endswith("file_id.ans"):
default_width = 44 # default with for file_id.diz
newFrame = dur_ansiparse.parse_ansi_escape_codes(raw_text, filename = filename, appState=self.appState, caller=self, debug=self.appState.debug, maxWidth=self.appState.wrapWidth)
self.appState.topLine = 0
self.appState.firstCol = 0
newMovieOpts = Options(width=newFrame.width, height=newFrame.height)
newMovie = Movie(newMovieOpts)
# add the frame with the loaded ANSI file to the movie
newMovie.addFrame(newFrame)
newMovie.deleteCurrentFrame() # remove the blank first frame
self.mov = newMovie
f.close()
#self.notify(f"From color map at 1, 1: {self.mov.currentFrame.newColorMap[1][1]}")
#for x in range(lineNum, self.mov.sizeY): # clear out rest of contents.
# self.mov.currentFrame.content[x] = list(" " * self.mov.sizeX)
# If we're in the wrong color mode, switch modes and reload file.
if self.appState.colorMode == "256":
if not self.mov.contains_high_colors(): # if not using 256 colors
#self.notify("Does not contain extended colors.")
if self.mov.contains_background_colors(): # but using background colors...
#self.notify("Contains background colors.")
# Must be a 16 color ANSI. Switch since 256 can't do background colors.
if self.appState.debug:
self.notify(f"16 color file. Switching to 16 color mode and reloading file.")
self.switchTo16ColorMode()
self.loadFromFile(shortfile, 'ascii')
# If drawing does contain high colors, and backgrounds... remove the backgrounds until 256 bg colors works.
if self.mov.contains_high_colors():
if self.mov.contains_background_colors():
self.mov.strip_backgrounds()
self.hardRefresh()
elif loadFormat == 'dur':
try:
f = open(filename, 'rb')
except Exception as e:
self.notify(f"Could not open file for reading: {type(e)}: {e}")
return None
# check for gzipped file
f.seek(0)
fileHeader = f.read(2)
if self.appState.debug2: self.notify(f"File header: {fileHeader.hex()}")
if self.appState.debug2: self.notify(f"Checking for gzip file...")
if fileHeader == b'\x1f\x8b': # gzip magic numbers
if self.appState.debug2: self.notify(f"gzip found")
# file == gzip compressed
f.close()
try:
f = gzip.open(filename, 'rb')
f.seek(0)
if self.appState.debug2: self.notify(f"Un-gzipped successfully")
except Exception as e:
self.notify(f"Could not open file for reading as gzip: {type(e)}: {e}", pause=True)
else:
if self.appState.debug2: self.notify(f"gzip NOT found")
#f.seek(0)
# check for JSON Durdraw file
#if (f.read(16) == b'\x7b\x0a\x20\x20\x22\x44\x75\x72\x64\x72\x61\x77\x20\x4d\x6f\x76'): # {. "Durdraw Mov
if self.appState.debug2: self.notify(f"Checking for JSON file.")
f.seek(0)
if f.read(12) == b'\x7b\x0a\x20\x20\x22\x44\x75\x72\x4d\x6f\x76\x69': # {. "DurMov
if self.appState.debug2: self.notify(f"JSON found. Loading JSON dur file.")
f.seek(0)
fileColorMode, fileCharEncoding = durfile.get_dur_file_colorMode_and_charMode(f)
self.appState.fileColorMode = fileColorMode
if fileColorMode == "256" and fileColorMode != self.appState.colorMode and self.appState.maxColors == 16:
if not self.appState.playOnlyMode:
self.notify(f"Loaded 256 color file in 16 color mode.")
if fileColorMode == "256" and fileColorMode != self.appState.colorMode and self.appState.maxColors > 255:
#self.notify(f"Warning: Loading a 256 color file in 16 color mode. Some colors may not be displayed.")
if self.appState.debug:
self.notify(f"256 color file. Switching to 256 color mode.")
self.switchTo256ColorMode()
self.loadFromFile(shortfile, 'dur')
#if fileCharEncoding != self.appState.charEncoding:
# self.notify(f"Warning: File uses {fileCharEncoding} character encoding, but Durdraw is in {self.appState.charEncoding} mode.")
newMovie = durfile.open_json_dur_file(f, self.appState)
self.opts = newMovie['opts']
self.mov = newMovie['mov']
self.setPlaybackRange(1, self.mov.frameCount)
if self.appState.debug2: self.notify(f"{self.opts}")
if self.appState.debug2: self.notify(f"Finished loading JSON dur file")
self.appState.curOpenFileName = os.path.basename(filename)
self.appState.modified = False
#self.setWindowTitle(shortfile)
self.convertToCurrentFormat(fileColorMode = fileColorMode)
# Convert palettes as necessary
if fileColorMode == "xterm-256" and fileColorMode != self.appState.colorMode:
# Old file format, does not specify whether it's 16 or 256 colors.
fileColorMode = "256"
pass
if fileColorMode == "16" and fileColorMode != self.appState.colorMode:
#self.notify(f"Warning: Loading 16 color ANSI in {self.appState.colorMode} color mode will lose background colors.", pause=True)
if self.appState.debug:
self.notify(f"16 color file. Switching to 16 color mode.")
self.switchTo16ColorMode()
self.loadFromFile(shortfile, 'dur')
self.hardRefresh()
shortpath = os.path.split(filename)[0]
if len(os.path.split(shortpath)) > 1:
shortpath = os.path.split(shortpath)[1]
self.appState.fileShortPath = shortpath
#self.appState.fileLongPath = fullpath
if self.appState.colorMode == "256":
if self.mov.contains_background_colors():
self.mov.strip_backgrounds()
# Remove un-printable characters, eg: errant \n from old .dur files, where enter accidentally inserted ^Ms.
self.mov.strip_unprintable_characters()
return True
try: # Maybe it's a really old Pickle file...
if self.appState.debug2: self.notify(f"Unpickling..")
pickle_fail = False
f.seek(0)
unpickler = durfile.DurUnpickler(f)
if self.appState.debug2: self.notify(f"self.opts = unpickler.load()")
self.opts = unpickler.load()
if self.appState.debug2: self.notify(f"self.mov = unpickler.load()")
self.mov = unpickler.load()
if self.appState.debug2: self.notify(f"self.appState.curOpenFileName = os.path.basename(filename)")
self.appState.curOpenFileName = os.path.basename(filename)
if self.appState.debug2: self.notify(f"self.appState.playbackRange = (1,self.mov.frameCount)")
self.appState.playbackRange = (1,self.mov.frameCount)
except Exception as e:
pickle_fail = True
if self.appState.debug2:
self.notify(f"Exception in unpickling: {type(e)}: {e}")
# If the first unpickling fails, try looking for another pickle format
if pickle_fail:
try:
f.seek(0)
if self.appState.debug2: self.notify(f"self.opts = pickle.load(f ")
self.opts = pickle.load(f)
if self.appState.debug2: self.notify(f"self.mov = pickle.load(f ")
self.mov = pickle.load(f)
self.appState.playbackRange = (1,self.mov.frameCount)
pickle_fail = False
except Exception as e:
if self.appState.debug2:
self.notify(f"Exception in unpickling other format: {type(e)}: {e}")
pickle_fail = True
if pickle_fail: # pickle is still failing
loadFormat = 'ascii' # loading .dur format failed, so assume it's ascii instead.
# change this to ANSI once ANSI file loading works, stripping out ^M in newlines
# change this whole method to call loadDurFile(), loadAnsiFile(),
# etc, checking for faiulre.
self.convertToCurrentFormat()
f.close()
if loadFormat == 'ascii': # loading as dur failed, so load as ascii instead.
self.loadFromFile(shortfile, loadFormat)
self.appState.modified = False
#self.setWindowTitle(shortfile)
shortpath = os.path.split(filename)[0]
if len(os.path.split(shortpath)) > 1:
shortpath = os.path.split(shortpath)[1]
self.appState.fileShortPath = shortpath
#self.appState.fileLongPath = fullpath
self.mov.gotoFrame(1)
self.hardRefresh()
def save(self):
self.clearStatusLine()
self.move(self.mov.sizeY, 0)
self.promptPrint("File format? [D]UR, [A]NSI, A[N]SIMATION, ASCI[I], [M]IRC, [J]SON, [H]TML, [P]NG, [G]IF: ")
self.stdscr.nodelay(0) # do not wait for input when calling getch
prompting = True
saved = False
while prompting:
c = self.stdscr.getch()
time.sleep(0.01)
if c in [105, 73]: # 105 i = ascii
saveFormat = 'ascii'
prompting = False
elif c in [100, 68]: # 100 = d = dur, 68 = D
saveFormat = 'dur'
prompting = False
elif c in [106, 74]: # 106 = j, 74 = J
saveFormat = 'json'
prompting = False
elif c in [104, 63]: # 106 = h, 74 = H
saveFormat = 'html'
prompting = False
elif c in [97, 65]: # a = ansi
saveFormat = 'ansi'
prompting = False
elif c in [110, 78]: # 110 = n, 78 = N, ansimation
saveFormat = 'ansimation'
prompting = False
elif c in [109]: # m = mIRC
prompting = False
if self.mov.contains_high_colors():
self.notify("Sorry, mIRC export only works for 16 colors, but this art contains extended colors.", pause=True)
return None
else:
saveFormat = 'irc'
elif c in [112, 80]: # p = png
saveFormat = 'png'
prompting = False
elif c in [103, 71]: # g = gif
saveFormat = 'gif'
prompting = False
elif c == 27: # 27 = esc = cancel
self.clearStatusLine()
prompting = False
return None
self.clearStatusLine()
if saveFormat == 'ascii' or saveFormat == 'ansi' or saveFormat == 'ansimation': # ansi = escape codes for colors+ascii
prompting = True
while prompting:
# Ask if they want CP437 or Utf-8 encoding
self.clearStatusLine()
self.promptPrint(f"Character encoding to save with? [C]P437, [U]tf-8? (default: {self.appState.charEncoding}): ")
c = self.stdscr.getch()
time.sleep(0.01)
if c == ord('c'):
encoding = "cp437"
prompting = False
elif c == ord('u'):
encoding = "utf-8"
prompting = False
elif c in [13, curses.KEY_ENTER]:
encoding = self.appState.charEncoding
prompting = False
elif c == 27: # 27 = esc = cancel
self.notify("Canceled. File not saved.")
prompting = False
return False
if saveFormat == 'ansimation':
prompting = True
repeat = False
while prompting:
# Ask if they want CP437 or Utf-8 encoding
self.clearStatusLine()
self.promptPrint("Repeat or loop animation? [no]: ")
c = self.stdscr.getch()
if c == ord('y'):
repeat = True
prompting = False
elif c == ord('n'):
repeat = False
prompting = False
elif c in [13, curses.KEY_ENTER]:
repeat = False
prompting = False
elif c == 27: # 27 = esc = cancel
self.notify("Canceled. File not saved.")
prompting = False
return False
if repeat:
self.clearStatusLine()
self.promptPrint("Repeat how many times? ")
curses.echo()
repeat_string = str(self.stdscr.getstr().decode('utf-8'))
curses.noecho()
try:
repeat = int(repeat_string)
except ValueError:
self.notify("Error: Repeat value must be an integer. File not saved.")
return False
if saveFormat in ['png', 'gif']:
PIL_found = False
try:
import PIL
PIL_found = True
except ImportError:
PIL_found = False
if not PIL_found:
self.notify("Error: PNG and GIF export requires the Pillow module for Python.")
return False
self.promptPrint("Which font? IBM PC [A]NSI, AM[I]GA: ")
prompting = True
while prompting:
time.sleep(0.01)
c = self.stdscr.getch()
time.sleep(0.01)
if c in [97, 65]: # a/A = ansi
saveFont = 'ansi'
prompting = False
elif c in [105, 68]: # 105 i/I = amiga
saveFont = 'amiga'
prompting = False
elif c == 27: # 27 = esc = cancel
self.clearStatusLine()
prompting = False
return None
if saveFont == 'amiga':
self.clearStatusLine()
self.promptPrint("Which amiga font? 1=topaz 2=b-strict, 3=microknight, 4=mosoul, 5=pot-noodle: ")
prompting = True
while prompting:
c = self.stdscr.getch()
time.sleep(0.01)
if c == 49: # 1 = topaz
saveFont = 'topaz'
prompting = False
if c == 50: # 2 = b-strict
saveFont = 'b-strict'
prompting = False
if c == 51: # 3 = microknight
saveFont = 'microknight'
prompting = False
if c == 52: # 4 = mosoul
saveFont = 'mosoul'
prompting = False
if c == 53: # 5 = pot-noodle
saveFont = 'pot-noodle'
prompting = False
elif c == 27: # 27 = esc = cancel
self.clearStatusLine()
prompting = False
return None
self.clearStatusLine()
if saveFormat == "ansi" or saveFormat == "ansimation":
self.promptPrint(f"Enter file name to save as ({saveFormat}/{encoding}) [{self.appState.curOpenFileName}]: ")
else:
self.promptPrint(f"Enter file name to save as ({saveFormat}) [{self.appState.curOpenFileName}]: ")
curses.echo()
filename = str(self.stdscr.getstr().decode('utf-8'))
curses.noecho()
if filename.replace(' ', '') == '':
if saveFormat == "dur":
filename = self.appState.curOpenFileName
else:
self.notify("File name cannot be empty.")
return False
try:
filename = os.path.expanduser(filename)
except: # possibly due to funk in filename, prevent possible crash
self.promptPrint("There was an error saving to that file.")
return False
# If file exists.. ask if it should overwrite.
if os.path.exists(filename):
self.clearStatusLine()
self.promptPrint(f"This file already exists. Overwrite? ")
prompting = True
while prompting:
c = self.stdscr.getch()
time.sleep(0.01)
if c in [121, 89]: # y or Y
prompting = False
if c in [110, 78]: # n or N
prompting = False
self.notify("Canceled. File not saved.")
return False
if saveFormat == 'ascii':
saved = self.saveAsciiFile(filename, encoding=encoding)
if saveFormat == 'durOld': # dur Old = pickled python objects (ew)
saved = self.saveDurFile(filename)
if saveFormat == 'dur': # dur2 = serialized json gzipped
saved = self.saveDur2File(filename)
if saveFormat == 'json': # dur2 = serialized json, plaintext
saved = self.saveDur2File(filename, gzipped=False)
if saveFormat == 'html': # dur2 = serialized json, plaintext
saved = self.saveHtmlFile(filename, gzipped=False)
if saveFormat == 'ansi': # ansi = escape codes for colors+ascii
saved = self.saveAnsiFile(filename, encoding=encoding)
if saveFormat == 'ansimation': # ansi = escape codes for colors+ascii
saved = self.saveAnsimationFile(filename, encoding=encoding, play_times=repeat)
if saveFormat == 'irc': # ansi = escape codes for colors+ascii
saved = self.saveAnsiFile(filename, ircColors = True)
if saveFormat == 'png': # png = requires ansi love
saved = self.savePngFile(filename, font=saveFont)
if saveFormat == 'gif': # gif = requires PIL
saved = self.saveGifFile(filename, font=saveFont)
if saved:
self.notify("*Saved* (Press any key to continue)", pause=True)
self.appState.curOpenFileName = os.path.basename(filename)
self.appState.modified = False
self.undo.modifications = 0
#self.setWindowTitle(self.appState.curOpenFileName)
elif not saved:
self.notify("Save failed.")
def saveDurFile(self, filename):
# open and write file
try:
f = gzip.open(filename, 'wb')
except:
self.notify("Could not open file for writing. (Press any key to continue)", pause=True)
return False
pickle.dump(self.opts, f)
pickle.dump(self.mov, f)
f.close()
return True
def saveHtmlFile(self, filename, gzipped=False):
# open and write file
try:
f = gzip.open(filename, 'w')
except:
self.notify("Could not open file for writing. (Press any key to continue)", pause=True)
return False
f.close()
durfile.write_frame_to_html_file(self.mov, self.appState, self.mov.currentFrame, filename, gzipped=gzipped)
return True
def saveDur2File(self, filename, gzipped=True):
# open and write file
try:
f = gzip.open(filename, 'w')
except:
self.notify("Could not open file for writing. (Press any key to continue)", pause=True)
return False
f.close()
if gzipped:
durfile.serialize_to_json_file(self.opts, self.appState, self.mov, filename)
else:
durfile.serialize_to_json_file(self.opts, self.appState, self.mov, filename, gzipped=False)
return True
def saveAsciiFile(self, filename, encoding = "default"):
""" Saves to ascii file, strips trailing blank lines """
try:
if encoding == "default":
f = open(filename, 'w')
elif encoding == "cp437":
f = open(filename, 'w', encoding="cp437")
elif encoding == "utf-8":
f = open(filename, 'w', encoding="utf-8")
except:
self.notify("Could not open file for writing. (Press any key to continue)", pause=True)
return False
# rewrite this. rstrip(' ') looks cool, though.
outBuffer = '\n'.join(''.join(line).rstrip(' ') for line in self.mov.currentFrame.content).rstrip('\n')
try:
f.write(outBuffer + '\n\n')
except UnicodeEncodeError:
self.notify(f"Error - some characters cannot be saved using encoding {encoding}. (Press any key to continue)", pause=True)
f.close()
return False
f.close()
return True
def frameToAnsiCodes(self, frame):
""" Takes a Durdraw frame, returns a string of escape codes which can be written to a file """
pass
def saveAnsimationFile(self, filename, lastLineNum=False, lastColNum=False, firstColNum=False, firstLineNum=None, ircColors=False, encoding="default", play_times=False):
""" Saves current frame of current movie to ansi file """
# some escape codes from https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
#print("\\033[;H or \\033[;f - Put the cursor at line L and column C.")
#print("\\033[A - Move the cursor up N lines")
#print("\\033[B - Move the cursor down N lines")
#print("\\033[C - Move the cursor forward N columns")
#print("\\033[D - Move the cursor backward N columns\n")
#print("\\033[2J - Clear the screen, move to (0,0)")
ESC_CLS = "\033[2J"
ESC_TOPLEFT = "\033[0;0H"
if play_times == False:
play_times = 1
try:
if encoding == "default":
f = open(filename, 'w')
elif encoding == "cp437":
f = open(filename, 'w', encoding="cp437")
elif encoding == "utf-8":
f = open(filename, 'w', encoding="utf-8")
except:
self.notify("Could not open file for writing. (Press any key to continue)", pause=True)
return False
string = ''
string = string + ESC_CLS
string = string + ESC_TOPLEFT
if not lastLineNum: # if we weren't told what lastLineNum is...
# find it (last line we should save)
lastLineNum = self.findFrameLastLine(self.mov.currentFrame)
for frame in self.mov.frames:
newLastLineNum = self.findFrameLastLine(frame)
lastLineNum = max(newLastLineNum, lastLineNum)
#if newLastLineNum > lastLineNum:
# lastLineNum = newLastLineNum
if not lastColNum:
lastColNum = self.findFrameLastCol(self.mov.currentFrame)
for frame in self.mov.frames:
newLastColNum = self.findFrameLastCol(frame)
lastColNum = max(newLastColNum, lastColNum)
if not firstColNum:
firstColNum = 0 # Don't crop leftmost blank columns
if not firstLineNum:
#firstLineNum = self.findFrameFirstLine(self.mov.currentFrame)
firstLineNum = 0
for repeat_count in range(0, play_times):
frameNum = 0
for frame in self.mov.frames:
string = string + ESC_TOPLEFT
for lineNum in range(firstLineNum, lastLineNum): # y == lines
for colNum in range(firstColNum, lastColNum):
char = self.mov.frames[frameNum].content[lineNum][colNum]
color = self.mov.frames[frameNum].newColorMap[lineNum][colNum]
colorFg = color[0]
colorBg = color[1]
if self.appState.colorMode == "256":
if self.appState.showBgColorPicker == False:
colorBg = 0 # black, I hope
try:
if ircColors:
colorCode = self.ansi.getColorCodeIrc(colorFg,colorBg)
else:
if self.appState.colorMode == "256":
colorCode = self.ansi.getColorCode256(colorFg,colorBg)
else:
colorCode = self.ansi.getColorCode(colorFg,colorBg)
except KeyError:
if ircColors: # problem? set a default color
colorCode = self.ansi.getColorCodeIrc(1,0)
else:
colorCode = self.ansi.getColorCode(1,0)
# If we don't have extended ncurses 6 color pairs,
# we don't have background colors.. so write the background as black/0
string = string + colorCode + char
if ircColors:
string = string + '\n'
else:
string = string + '\r\n'
frameNum += 1
try:
f.write(string)
saved = True
except UnicodeEncodeError as encodeError:
self.notify("Error: Some characters were not compatible with this encoding. File not saved.", pause=True)
self.notify(f"{encodeError}", pause=True)
saved = False
#f.close()
#return False
if ircColors:
string2 = string + '\n'
else:
f.write('\033[0m') # color attributes off
string2 = string + '\r\n' # final CR+LF (DOS style newlines)
f.close()
#return True
return saved
def saveAnsiFile(self, filename, lastLineNum=False, lastColNum=False, firstColNum=False, firstLineNum=None, ircColors=False, encoding="default"):
""" Saves current frame of current movie to ansi file """
try:
if encoding == "default":
f = open(filename, 'w')
elif encoding == "cp437":
f = open(filename, 'w', encoding="cp437")
elif encoding == "utf-8":
f = open(filename, 'w', encoding="utf-8")
except:
self.notify("Could not open file for writing. (Press any key to continue)", pause=True)
return False
string = ''
if not lastLineNum: # if we weren't told what lastLineNum is...
# find it (last line we should save)
lastLineNum = self.findFrameLastLine(self.mov.currentFrame)
if not lastColNum:
lastColNum = self.findFrameLastCol(self.mov.currentFrame)
if not firstColNum:
firstColNum = 0 # Don't crop leftmost blank columns
if not firstLineNum:
#firstLineNum = self.findFrameFirstLine(self.mov.currentFrame)
firstLineNum = 0 # also don't crop leading blank lines. rude
error_encoding = False
for lineNum in range(firstLineNum, lastLineNum): # y == lines
for colNum in range(firstColNum, lastColNum):
char = self.mov.currentFrame.content[lineNum][colNum]
color = self.mov.currentFrame.newColorMap[lineNum][colNum]
colorFg = color[0]
colorBg = color[1]
if self.appState.colorMode == "256":
if self.appState.showBgColorPicker == False:
colorBg = 0 # black, I hope
try:
if ircColors:
colorCode = self.ansi.getColorCodeIrc(colorFg,colorBg)
else:
if self.appState.colorMode == "256":
colorCode = self.ansi.getColorCode256(colorFg,colorBg)
else:
colorCode = self.ansi.getColorCode(colorFg,colorBg)
except KeyError:
if ircColors: # problem? set a default color
colorCode = self.ansi.getColorCodeIrc(1,0)
else:
colorCode = self.ansi.getColorCode(1,0)
# Make sure encoding this character encodes correctly
try:
#char.encode('cp437')
char.encode(encoding)
except UnicodeEncodeError as encodeError:
self.notify("Error: Some characters were not compatible with this encoding. File not saved.", pause=True)
self.notify(f"line: {lineNum}, col: {colNum} ", pause=True)
saved = False
error_encoding = True
break
# If we don't have extended ncurses 6 color pairs,
# we don't have background colors.. so write the background as black/0
string = string + colorCode + char
if ircColors:
string = string + '\n'
else:
#string = string + '\r\n'
string = string + '\n'
if not error_encoding:
try:
f.write(string)
saved = True
except UnicodeEncodeError as encodeError:
self.notify("Error: Some characters were not compatible with this encoding. File not saved.", pause=True)
self.notify(f"{encodeError}", pause=True)
saved = False
#f.close()
#return False
if ircColors:
string2 = string + '\n'
else:
f.write('\033[0m') # color attributes off
string2 = string + '\r\n' # final CR+LF (DOS style newlines)
f.close()
#return True
return saved
def findLastMovieLine(self, movie):
""" Cycle through the whole movie, figure out the lowest numbered
line that == blank on all frames, return that #. Used to trim blank
lines when saving. """
movieLastLine = 1
for frame in movie.frames:
frameLastLine = self.findFrameLastLine(frame)
if frameLastLine > movieLastLine:
movieLastLine = frameLastLine
return movieLastLine
def findFirstMovieLine(self, movie):
""" Cycle through the whole movie, figure out the highest numbered
line that == blank on all frames, return that #. Used to trim blank
lines when saving. """
movieFirstLine = movie.sizeY
for frame in movie.frames:
frameFirstLine = self.findFrameFirstLine(frame)
if frameFirstLine < movieFirstLine:
movieFirstLine = frameFirstLine
return movieFirstLine
def findFirstMovieCol(self, movie):
""" Cycle through the whole movie, figure out the leftmost column that
== blank on all frames, return that #. Used to trim blank lines
when saving. """
movieFirstCol = movie.sizeX
for frame in movie.frames:
frameFirstCol = self.findFrameFirstCol(frame)
if frameFirstCol < movieFirstCol:
movieFirstCol = frameFirstCol
return movieFirstCol
def findLastMovieCol(self, movie):
""" Cycle through the whole movie, figure out the rightmost column
that == blank on all frames, return that #. Used to trim blank
lines when saving. """
movieLastCol = 1
for frame in movie.frames:
frameLastCol = self.findFrameLastCol(frame)
if frameLastCol > movieLastCol:
movieLastCol = frameLastCol
return movieLastCol
def findFrameFirstLine(self, frame):
""" For the given frame, figure out the first non-blank line, return
that # (aka, first used line of the frame) """
# start at the first line, work up until we find a character.
for lineNum in range(0, frame.sizeY):
for colNum in range(0, frame.sizeX):
try:
if not frame.content[lineNum][colNum] in [' ', '']:
return lineNum # we found a non-empty character
except Exception as E:
pdb.set_trace()
return 1 # blank frame, only save the first line.
def findFrameLastLine(self, frame):
""" For the given frame, figure out the last non-blank line, return
that # + 1 (aka, last used line of the frame) """
# start at the last line, work up until we find a character.
for lineNum in reversed(list(range(0, self.mov.sizeY))):
#for colNum in range(0, frame.sizeX):
for colNum in range(0, len(frame.content[lineNum])):
if not frame.content[lineNum][colNum] in [' ', '']:
return lineNum + 1 # we found a non-empty character
return 1 # blank frame, only save the first line.
def findFrameLastCol(self, frame):
""" For the given frame, figure out the last non-blank column, return
that # + 1 (aka, last used column of the frame) """
# start at the last column, work back until we find a character.
#for colNum in reversed(list(range(0, frame.sizeX))):
# for lineNum in reversed(list(range(0, frame.sizeY))):
for colNum in reversed(range(0, frame.sizeX)):
for lineNum in reversed(range(0, frame.sizeY)):
try:
if not frame.content[lineNum][colNum] in [' ', '']:
return colNum + 1 # we found a non-empty character
except Exception as E:
pdb.set_trace()
#pdb.set_trace()
return 1 # blank frame, only save the first line.
def findFrameFirstCol(self, frame):
""" For the given frame, figure out the first non-blank column, return
that # (aka, first used column of the frame) """
# start at the first column, work forward until we find a character.
for colNum in range(0, frame.sizeX):
for lineNum in range(0, frame.sizeY):
if not frame.content[lineNum][colNum] in [' ', '']:
return colNum # we found a non-empty character
return 1 # blank frame, only save the first line.
def savePngFile(self, filename, lastLineNum=None, firstLineNum=None, firstColNum=None, lastColNum=None, font='ansi'):
""" Save to ANSI then convert to PNG """
if not self.appState.isAppAvail("ansilove"): # ansilove not found
self.notify("Ansilove not found in path. Please find it at https://www.ansilove.org/", pause=True)
return False
PIL_found = False
try:
import PIL
PIL_found = True
except ImportError:
PIL_found = False
if not PIL_found:
self.notify("Error: PNG and GIF export requires the Pillow module for Python.")
return False
tmpAnsiFileName = filename + '.tmp.ans' # remove this file when done
tmpPngFileName = filename + '.tmp.ans.png' # remove this file when done
if not self.saveAnsiFile(tmpAnsiFileName, lastLineNum=lastLineNum, firstLineNum=firstLineNum, firstColNum=firstColNum, lastColNum=lastColNum, encoding="cp437"):
self.notify("Saving ansi failed, make sure you can write to current directory.")
return False
devnull = open('/dev/null', 'w')
if font == 'ansi':
ansiLoveReturn = subprocess.call(['ansilove', '-c', str(self.mov.sizeX + 1), tmpAnsiFileName, tmpPngFileName], stdout=devnull)
else: # amiga font
ansiLoveReturn = subprocess.call(['ansilove', '-c', str(self.mov.sizeX + 1), tmpAnsiFileName, "-f", font], stdout=devnull)
devnull.close()
os.remove(tmpAnsiFileName)
if ansiLoveReturn == 0: # this doesnt seem right either, as ansilove always
pass # returns True.
else:
self.notify("Ansilove didn't return success.")
return False # stop trying to save png
# crop out rightmost blank space
if not lastColNum:
lastColNum = self.findFrameLastCol(self.mov.currentFrame)
if not firstColNum:
#firstColNum = self.findFrameFirstCol(self.mov.currentFrame)
# ^ don't truncate the first columns. That's rude.
firstColNum = 0
characterWidth = 8 # 9 pixels wide
from PIL import Image
cropImage = Image.open(tmpPngFileName)
cropWidthPixels = (lastColNum - firstColNum) * characterWidth # right
w,h = cropImage.size
cropBox = (0, 0, cropWidthPixels, h) # should be right
cropImage = cropImage.crop(cropBox)
finalImage = open(filename, 'wb')
try:
cropImage.save(finalImage, "png")
#self.notify("Saved successfully!")
finalImage.close()
except:
self.notify("Error: Could not crop png.")
os.remove(tmpPngFileName)
return False
os.remove(tmpPngFileName)
return True
def saveGifFile(self, filename, font="ansi"):
try: # check for PIL/pillow
import PIL
except ImportError:
self.notify("Error: Please install the PIL python module.")
return False
tmpPngNames = []
# switch to first frame
self.mov.currentFrameNumber = 1
self.mov.currentFrame = self.mov.frames[self.mov.currentFrameNumber - 1]
self.refresh()
firstMovieLineNum = self.findFirstMovieLine(self.mov) # so we can trim
lastMovieLineNum = self.findLastMovieLine(self.mov)
firstMovieColNum = self.findFirstMovieCol(self.mov)
lastMovieColNum = self.findLastMovieCol(self.mov)
for num in range(1, self.mov.frameCount + 1): # then for each frame
# make a temp png filename and add it to the list
tmpPngName = filename + "." + str(num) + ".png"
tmpPngNames.append(tmpPngName)
# save the png
if not self.savePngFile(tmpPngName, lastLineNum=lastMovieLineNum, firstLineNum=firstMovieLineNum, firstColNum=firstMovieColNum, lastColNum=lastMovieColNum, font=font):
return False
# If frame has a delay, copy saved file FPS times and add new
# file names to tmpPngNames.
if self.mov.currentFrame.delay > 0:
numOfDelayFramesToAdd = int(self.mov.currentFrame.delay) * int(self.opts.framerate)
for delayFrameNum in range(1,int(self.mov.currentFrame.delay) * \
int(self.opts.framerate)):
# eg: 2 second delay, 8fps, means add 16 frames.
delayFramePngName = tmpPngName + str(delayFrameNum) + ".png"
shutil.copy(tmpPngName, delayFramePngName)
tmpPngNames.append(delayFramePngName)
# go to next frame
self.mov.nextFrame()
self.refresh()
# open all the pngs so we can save them to gif
from PIL import Image
pngImages = [Image.open(fn) for fn in tmpPngNames]
sleep_time = (1000.0 / self.opts.framerate) / 1000.0 # or 0.1 == good, too
self.pngsToGif(filename, pngImages, sleep_time)
for rmFile in tmpPngNames:
os.remove(rmFile)
return True
def pngsToGif(self, outfile, pngImages, sleeptime):
# create frames
frames = []
for frame in pngImages:
frames.append(frame)
# Save into a GIF file that loops forever
frames[0].save(outfile, format='GIF',
append_images=frames[1:],
save_all=True,
duration=sleeptime * 1000, loop=0)
def showHelp(self):
if self.appState.hasHelpFile:
#self.showAnimatedHelpScreen()
#self.showAnimatedHelpScreen(page=2)
self.showScrollingHelpScreen()
else:
self.showStaticHelp()
def showStaticHelp(self):
self.cursorOff()
self.stdscr.nodelay(0)
helpScreenText = '''
__ __
_| |__ __ _____ __| |_____ _____ __ __ __
/ _ | | | __| _ | __| _ | | | |\\
/_____|_____|__|__|_____|__|___\\____|________| | Durr....
\\_____________________________________________\\| v %s
alt-k - next frame alt-' - delete current line
alt-j - prev frame alt-/ - insert line
alt-n - iNsert current frame clone alt-, - delete current column.
alt-N - appeNd empty frame alt-. - insert new column
alt-p - start/stop Playback alt-c - Color picker (256 only)
alt-d - Delete current frame alt-m - Menu
alt-D - set current frame Delay F1-F10 - insert character
alt-+/alt-- increase/decrease FPS alt-z - undo
alt-M - Move current frame alt-r - Redo
alt-up - next fg color alt-s - Save
alt-down - prev fg color alt-o - Open
alt-right - next bg color alt-q - Quit
alt-left - prev bg color alt-h - Help
alt-R - set playback/edit Range alt-pgdn - next character set
alt-g - Go to frame # alt-pgup - prev character set
Help file could not be found. You might want to reinstsall Durdraw...
Can use ESC or META instead of ALT
''' % self.appState.durVer
# remove the blank first line from helpScreenText..
# it's easier to edit here with the blank first line.
helpScreenText = '\n'.join(helpScreenText.split('\n')[1:])
self.clearStatusLine()
self.addstr(0, 0, helpScreenText)
self.promptPrint("* Press the ANY key (or click) to continue *")
self.stdscr.getch()
self.stdscr.clear()
self.cursorOn()
if self.playing == True:
self.stdscr.nodelay(1)
def activate_heat_code(self):
# hell's inferno
if self.appState.inferno == None:
inferno_fullpath = pathlib.Path(__file__).parent.joinpath("help/inferno.dur")
self.appState.inferno, self.appState.inferno_opts = self.appState.loadDurFileToMov(inferno_fullpath)
self.enterViewMode(mov=self.appState.inferno, opts=self.appState.inferno_opts)
#self.notify("heat code activated")
def hardRefresh(self):
#self.stdscr.clear()
self.stdscr.redrawwin()
#self.refresh()
def refresh(self, refreshScreen=True): # rename to redraw()?
"""Refresh the screen"""
topLine = self.appState.topLine
if self.appState.playingHelpScreen_2:
mov = self.appState.helpMov_2
elif self.appState.playingHelpScreen:
mov = self.appState.helpMov
else:
mov = self.mov
# Figure out the last line to draw
lastLineToDraw = topLine + self.appState.realmaxY - 2 # right above the status line
if self.appState.playOnlyMode:
lastLineToDraw += 2
if lastLineToDraw > mov.sizeY:
lastLineToDraw = mov.sizeY
screenLineNum = 0
firstCol = self.appState.firstCol
lastCol = min(mov.sizeX, self.appState.realmaxX + firstCol)
# Draw each character
for linenum in range(topLine, lastLineToDraw):
line = mov.currentFrame.content[linenum]
for colnum in range(firstCol, lastCol):
charColor = mov.currentFrame.newColorMap[linenum][colnum]
charContent = str(line[colnum])
if self.appState.cursorMode == "Paint" and not self.playing and not self.appState.playingHelpScreen:
if self.appState.brush != None:
# draw brush preview
# If we're drawing within the brush area:
if linenum in range(self.appState.mouse_line + self.appState.topLine, self.appState.mouse_line + self.appState.brush.sizeX + self.appState.topLine):
if colnum in range(self.appState.mouse_col + self.appState.firstCol, self.appState.mouse_col + self.appState.brush.sizeY + self.appState.firstCol):
#brush_line = linenum - self.appState.mouse_line
brush_line = linenum - self.appState.mouse_line - self.appState.topLine
brush_col = colnum - self.appState.mouse_col - self.appState.firstCol
try:
brushChar = self.appState.brush.content[brush_col][brush_line]
except IndexError:
# This should really never happen now.
self.notify(f"Index error: bcol: {brush_col}, bline: {brush_line}, col: {colnum}, line: {linenum}, mcol: {self.appState.mouse_col}, {self.appState.mouse_line}", pause=False)
brushChar = ' '
# invisible background for brushes
if brushChar == ' ':
pass
else:
# It's a character that we should draw as a brush preview
if self.appState.renderMouseCursor:
charContent = brushChar
charColor = self.appState.brush.newColorMap[brush_col][brush_line]
if linenum == self.appState.mouse_line + self.appState.topLine and colnum == self.appState.mouse_col + self.appState.firstCol:
if self.appState.cursorMode == "Draw" and not self.playing and not self.appState.playingHelpScreen: # Drawing preview instead
if self.appState.renderMouseCursor:
charContent = self.appState.drawChar
charColor = [self.colorfg, self.colorbg]
try:
# set ncurss color pair
cursesColorPair = self.ansi.colorPairMap[tuple(charColor)]
except: # Or if we can't, fail to the terminal's default color
cursesColorPair = 0
injecting = False
if self.appState.can_inject and self.appState.colorMode == "256":
injecting = True
if charColor[0] > 8 and charColor[0] <= 16 and self.appState.colorMode == "16": # bright color
self.addstr(screenLineNum, colnum - self.appState.firstCol, charContent, curses.color_pair(cursesColorPair) | curses.A_BOLD)
elif charColor[0] > 7 and charColor[0] <= 15 and self.appState.colorMode == "256": # bright color
# Let's try injecting!
if injecting:
moveCode = f"\x1b[{screenLineNum + 1};{colnum - self.appState.firstCol + 1}H"
colorCode = self.ansi.getColorCode256(charColor[0],charColor[1])
sys.stdout.write(moveCode)
sys.stdout.write(colorCode)
sys.stdout.write(charContent)
pass
#self.addstr(screenLineNum, colnum - self.appState.firstCol, charContent)
else:
self.addstr(screenLineNum, colnum - self.appState.firstCol, charContent, curses.color_pair(cursesColorPair) | curses.A_BOLD)
# If the mouse cursor is over Fg: 1 Bg:1 in 16 color mode, aka Black on Black
# then print with defualt charaacters instead. This should prevent the cursor from
# disappearing, as well as let you preview "invisible" text under the cursor.
elif not self.appState.playOnlyMode and colnum + 1 == self.xy[1] and linenum == self.xy[0]: # under the cursor
if self.appState.colorMode == "16":
visible_color_pair = self.ansi.colorPairMap[(self.appState.defaultFgColor, self.appState.defaultBgColor)]
#self.addstr(screenLineNum, colnum, "X", visible_color_pair)
# black on black
if charColor[0] == 1 and charColor[1] == 0 or \
charColor[0] == 1 and charColor[1] == 8:
# make it show
self.addstr(screenLineNum, colnum, charContent, visible_color_pair)
# not black on black
else: # 16 color Normal character, under the cursor. No funny business. Print to the screen
self.addstr(screenLineNum, colnum - self.appState.firstCol, charContent, curses.color_pair(cursesColorPair))
else: # 256 color Normal character, under the cursor. No funny business. Print to the screen
self.addstr(screenLineNum, colnum - self.appState.firstCol, charContent, curses.color_pair(cursesColorPair))
else: # Normal character. No funny business. Print to the screen
#injecting = True
if injecting and charColor[1] != 0:
#self.stdscr.move(screenLineNum, colnum - self.appState.firstCol)
#self.stdscr.refresh()
moveCode = f"\x1b[{screenLineNum + 1};{colnum - self.appState.firstCol + 1}H"
colorCode = self.ansi.getColorCode256(charColor[0],charColor[1])
#sys.stdout.write(f"\x1b[38:5:{charColor[0]}m") # FG
sys.stdout.write(moveCode)
sys.stdout.write(colorCode)
#sys.stdout.write(f"\x1b[48:5:{charColor[1]}m") # BG
sys.stdout.write(charContent)
#time.sleep(0.001)
#self.stdscr.refresh()
#self.notify("Injected", wait_time=40)
#sys.stdout.write("\x1b[48:5:46m")
#sys.stdout.write("\x1b[38:5:19m")
#self.addstr(screenLineNum, colnum - self.appState.firstCol, f"{charContent}")
pass
#self.addstr(screenLineNum, colnum - self.appState.firstCol, charContent)
else:
self.addstr(screenLineNum, colnum - self.appState.firstCol, charContent, curses.color_pair(cursesColorPair))
# draw border on right edge of line
if self.appState.drawBorders and screenLineNum + self.appState.topLine < self.mov.sizeY:
self.addstr(screenLineNum, mov.sizeX, ": ", curses.color_pair(self.appState.theme['borderColor']))
screenLineNum += 1
# draw bottom border
#if self.appState.drawBorders and screenLineNum < self.realmaxY - 3 :
if self.appState.drawBorders and screenLineNum + self.appState.topLine == self.mov.sizeY:
if screenLineNum < self.statusBarLineNum:
borderWidth = min(mov.sizeX, self.realmaxX)
self.addstr(screenLineNum, 0, "." * borderWidth, curses.color_pair(self.appState.theme['borderColor']))
self.addstr(screenLineNum, mov.sizeX, ": ", curses.color_pair(self.appState.theme['borderColor']))
screenLineNum += 1
#spaceMultiplier = mov.sizeX + 1
spaceMultiplier = self.realmaxY
for x in range(screenLineNum, self.realmaxY - 2):
self.addstr(x, 0, " " * spaceMultiplier)
curses.panel.update_panels()
if self.appState.playingHelpScreen:
self.addstr(self.statusBarLineNum + 1, 0, "Up/Down, Pgup/Pgdown, Home/End or Mouse Wheel to scroll. Enter or Esc to exit.", curses.color_pair(self.appState.theme['promptColor']))
if refreshScreen:
self.stdscr.refresh()
def addColToCanvas(self):
self.undo.push() # window is big enough
fg, bg = self.appState.defaultFgColor, self.appState.defaultBgColor
for frameNum in range(0, len(self.mov.frames)):
for x in range(len(self.mov.frames[frameNum].content)):
self.mov.frames[frameNum].content[x].insert(self.xy[1] - 1, ' ')
self.mov.frames[frameNum].newColorMap[x].insert(self.xy[1] - 1, [fg,bg])
self.mov.frames[frameNum].sizeX += 1
self.mov.sizeX += 1
#self.mov.currentFrame.sizeX += 1
self.opts.sizeX += 1
self.hardRefresh()
def delColFromCanvas(self):
if self.mov.sizeX > 1 and self.xy[1] <= self.mov.sizeX:
self.undo.push()
for frameNum in range(0, len(self.mov.frames)): # Pop current column from every
for x in range(len(self.mov.frames[frameNum].content)): # line
self.mov.frames[frameNum].content[x].pop(self.xy[1] - 1)
self.mov.frames[frameNum].newColorMap[x].pop(self.xy[1] - 1)
self.mov.frames[frameNum].sizeX -= 1
self.mov.sizeX -= 1
#self.mov.currentFrame.sizeX -= 1
self.opts.sizeX -= 1
self.hardRefresh()
if self.xy[1] == self.mov.sizeX + 1:
self.move_cursor_left()
self.hardRefresh()
def addLineToCanvas(self):
self.undo.push()
fg = self.appState.defaultFgColor
bg = self.appState.defaultBgColor
for frameNum in range(0, len(self.mov.frames)):
self.mov.frames[frameNum].content.insert(self.xy[0] + 1, list(' ' * self.mov.sizeX))
self.mov.frames[frameNum].newColorMap.insert(self.xy[0] + 1, [[fg,bg]] * self.mov.sizeX)
self.mov.frames[frameNum].sizeY += 1
self.mov.sizeY += 1
#self.mov.currentFrame.sizeY += 1
self.opts.sizeY += 1
def delLineFromCanvas(self):
if self.mov.sizeY > 1 and self.xy[0] != self.mov.sizeY:
self.undo.push()
for frameNum in range(0, len(self.mov.frames)):
self.mov.frames[frameNum].content.pop(self.xy[0])
self.mov.frames[frameNum].newColorMap.pop(self.xy[0])
self.mov.frames[frameNum].sizeY -= 1
self.mov.sizeY -= 1
#self.mov.currentFrame.sizeY -= 1
self.opts.sizeY -= 1
self.hardRefresh()
if self.xy[0] == self.mov.sizeY: # We're on the last line, and just deleted it.
self.move_cursor_up()
def addCol(self, frange=None):
"""Insert column at position of cursor"""
fg, bg = self.appState.defaultFgColor, self.appState.defaultBgColor
self.undo.push()
if frange: # framge range
for frameNum in range(frange[0] - 1, frange[1]):
for x in range(len(self.mov.frames[frameNum].content)):
self.mov.frames[frameNum].content[x].insert(self.xy[1] - 1, ' ')
self.mov.frames[frameNum].content[x].pop()
self.mov.frames[frameNum].newColorMap[x].insert(self.xy[1] - 1, [fg,bg])
self.mov.frames[frameNum].newColorMap[x].pop()
else:
for x in range(len(self.mov.currentFrame.content)):
self.mov.currentFrame.content[x].insert(self.xy[1] - 1, ' ')
self.mov.currentFrame.content[x].pop()
self.mov.currentFrame.newColorMap[x].insert(self.xy[1] - 1, [fg,bg])
self.mov.currentFrame.newColorMap[x].pop()
# insert bit here to shift color map to the right from the column
# onward. how: start at top right character, work down to bottom
# copying the color from the character to the left.
# Then move left a column and repeat, etc, until you're at self.xy[1] -1 :).
self.refresh()
def delCol(self, frange=None):
"""Erase column at position of cursor"""
self.undo.push()
fg, bg = self.appState.defaultFgColor, self.appState.defaultBgColor
if frange: # framge range
for frameNum in range(frange[0] - 1, frange[1]):
for x in range(len(self.mov.frames[frameNum].content)): # Pop current column from every
self.mov.frames[frameNum].content[x].pop(self.xy[1] - 1) # line & add a blank
self.mov.frames[frameNum].content[x].append(' ') # at the end of each line.
self.mov.frames[frameNum].newColorMap[x].pop(self.xy[1] - 1) # line & add a blank
self.mov.frames[frameNum].newColorMap[x].append([fg,bg]) # at the end of each line.
else:
for x in range(len(self.mov.currentFrame.content)): # Pop current column from every
self.mov.currentFrame.content[x].pop(self.xy[1] - 1) # line & add a blank
self.mov.currentFrame.content[x].append(' ') # at the end of each line.
self.mov.currentFrame.newColorMap[x].pop(self.xy[1] - 1) # line & add a blank
self.mov.currentFrame.newColorMap[x].append([fg,bg]) # at the end of each line.
self.hardRefresh()
def delLine(self, frange=None):
"""delete current line"""
self.undo.push()
fg, bg = self.appState.defaultFgColor, self.appState.defaultBgColor
if frange:
for frameNum in range(frange[0] - 1, frange[1]):
self.mov.frames[frameNum].content.pop(self.xy[0])
self.mov.frames[frameNum].content.append([])
self.mov.frames[frameNum].content[len(self.mov.frames[frameNum].content) - 1] = list(' ' * self.mov.sizeX)
self.mov.frames[frameNum].newColorMap.pop(self.xy[0])
self.mov.frames[frameNum].newColorMap.append([])
self.mov.frames[frameNum].newColorMap[len(self.mov.frames[frameNum].newColorMap) - 1] = [[fg,bg]] * self.mov.sizeX
else:
self.mov.currentFrame.content.pop(self.xy[0])
self.mov.currentFrame.content.append([])
self.mov.currentFrame.content[len(self.mov.currentFrame.content) - 1] = list(' ' * self.mov.sizeX)
self.mov.currentFrame.newColorMap.pop(self.xy[0])
self.mov.currentFrame.newColorMap.append([])
self.mov.currentFrame.newColorMap[len(self.mov.currentFrame.newColorMap) - 1] = [[fg,bg]] * self.mov.sizeX
self.hardRefresh()
def addLine(self, frange=None):
"""Insert new line"""
fg, bg = self.appState.defaultFgColor, self.appState.defaultBgColor
self.undo.push()
if frange:
for frameNum in range(frange[0] - 1, frange[1]):
self.mov.frames[frameNum].content.insert(self.xy[0], list(' ' * self.mov.sizeX))
self.mov.frames[frameNum].content.pop()
self.mov.frames[frameNum].newColorMap.insert(self.xy[0], [[fg,bg]] * self.mov.sizeX)
self.mov.frames[frameNum].newColorMap.pop()
self.refresh()
else:
self.mov.currentFrame.content.insert(self.xy[0], list(' ' * self.mov.sizeX))
self.mov.currentFrame.content.pop()
self.mov.currentFrame.newColorMap.insert(self.xy[0], [[fg,bg]] * self.mov.sizeX)
self.mov.currentFrame.newColorMap.pop()
self.refresh()
def startSelecting(self, firstkey=None, mouse=False): # firstkey is the key the user was
#pressing. left, right, etc
"""Mark selection for copy/cut/move - trigger with shift-arrow-keys"""
# any other key returns (cancels)
# print message: "Select mode - Enter to select, Esc to cancel"
self.undo.push()
startPoint = [self.xy[0], self.xy[1]] # set to wherever the cursor is
endPoint = startPoint
selecting = True
self.stdscr.nodelay(0) # wait for getch input
c = firstkey
self.clearStatusBar()
#self.addstr(self.statusBarLineNum, 0, f"Use arrow keys to make selection, enter when done.")
while selecting:
endPoint = [self.xy[0], self.xy[1]] # set to wherever the cursor is
self.refresh()
self.addstr(self.statusBarLineNum + 1, 0, f"Use arrow keys to make selection, enter when done.")
# draw block area on top of drawing area
mov = self.mov
if endPoint[0] >= startPoint[0]: # if we're moving right of start point
firstLineNum = startPoint[0]
lastLineNum = endPoint[0]
else: # otherwise, we're moving left
lastLineNum = startPoint[0]
firstLineNum = endPoint[0]
if endPoint[1] >= startPoint[1]: # we're moving down from start point
firstColNum = startPoint[1]
lastColNum = endPoint[1]
else: # we're moving up from start point
lastColNum = startPoint[1]
firstColNum = endPoint[1]
# draw selected area inverse
for linenum in range(firstLineNum, lastLineNum + 1):
for colnum in range(firstColNum - 1, lastColNum):
#if colnum == self.mov.sizeX - 1: # prevent overflow on last line
# colnum -= 1
charColor = mov.currentFrame.newColorMap[linenum][colnum]
try: # set ncurss color pair
cursesColorPair = self.ansi.colorPairMap[tuple(charColor)]
except: # Or if we can't, fail to the terminal's default color
cursesColorPair = 0
self.addstr(linenum - self.appState.topLine, colnum - self.appState.firstCol, mov.currentFrame.content[linenum][colnum], curses.color_pair(cursesColorPair) | curses.A_REVERSE)
width = lastColNum - firstColNum + 1
height = lastLineNum - firstLineNum + 1
# end draw block area
#self.stdscr.redrawwin()
c = self.stdscr.getch()
if c in [98, curses.KEY_LEFT, curses.KEY_SLEFT]:
self.move_cursor_left()
elif c in [98, curses.KEY_RIGHT, curses.KEY_SRIGHT]:
#if colnum == self.mov.sizeX - 1: # prevent overflow on last line
self.move_cursor_right()
# 337 and 520 - shift-up, 336 and 513 = shift-down
elif c in [339, curses.KEY_PPAGE]: # page up
self.move_cursor_pgup()
elif c in [338, curses.KEY_NPAGE]: # page down
self.move_cursor_pgdown()
elif c in [98, curses.KEY_UP, 337, 520]:
self.move_cursor_up()
elif c in [98, curses.KEY_DOWN, 336, 513]:
self.move_cursor_down()
elif c in [339, curses.KEY_HOME]: # 339 = home
self.xy[1] = 1
elif c in [338, curses.KEY_END]: # 338 = end
self.xy[1] = self.mov.sizeX - 1
elif c in [13, curses.KEY_ENTER]:
# Ask user what operation they want, and then do it on the selected area
# copy, cut, fill, or copy into all frames :)
prompting = True
self.clearStatusBar()
self.promptPrint("[C]opy, Cu[t], [D]elete, [F]ill, Co[l]or, Flip [X/Y], New [B]rush, copy to [A]ll Frames in range? " )
while prompting:
prompt_ch = self.stdscr.getch()
if chr(prompt_ch) in ['c', 'C']: # Copy
self.copySegmentToClipboard([firstLineNum, firstColNum], height, width)
prompting = False
if chr(prompt_ch) in ['b', 'B']: # Make Brush
self.copySegmentToBrush([firstLineNum, firstColNum], height, width)
prompting = False
#if chr(prompt_ch) in ['m', 'M']: # move
# prompting = False
elif chr(prompt_ch) in ['x', 'X']: # flip horizontally
self.flipSegmentHorizontal([firstLineNum, firstColNum], height, width)
#prompting = False
self.refresh()
elif chr(prompt_ch) in ['y', 'Y']: # flip vertically
self.flipSegmentVertical([firstLineNum, firstColNum], height, width)
#prompting = False
self.refresh()
# self.undo.push()
# self.mov.currentFrame.flip_horizontal()
if chr(prompt_ch) in ['t', 'T']: # Cut to clipboard
self.clearStatusBar()
if self.mov.hasMultipleFrames():
self.promptPrint("Cut across all frames in playback range (Y/N)? ")
askingAboutRange = True
else:
self.copySegmentToClipboard([firstLineNum, firstColNum], height, width)
self.undo.push()
self.deleteSegment([firstLineNum, firstColNum], height, width)
askingAboutRange = False
while askingAboutRange:
prompt_ch = self.stdscr.getch()
if chr(prompt_ch) in ['y', 'Y']: # yes, all range
self.copySegmentToClipboard([firstLineNum, firstColNum], height, width)
self.undo.push()
self.deleteSegment([firstLineNum, firstColNum], height, width, frange=self.appState.playbackRange)
askingAboutRange = False
if chr(prompt_ch) in ['n', 'N']: # No, only one frame
self.copySegmentToClipboard([firstLineNum, firstColNum], height, width)
self.undo.push()
self.deleteSegment([firstLineNum, firstColNum], height, width)
askingAboutRange = False
elif prompt_ch == 27: # esc, cancel
askingAboutRange = False
prompting = False
elif chr(prompt_ch) in ['d', 'D']: # delete/clear
self.clearStatusBar()
if self.mov.hasMultipleFrames():
self.promptPrint("Delete across all frames in playback range (Y/N)? ")
askingAboutRange = True
else:
self.undo.push()
self.deleteSegment([firstLineNum, firstColNum], height, width)
askingAboutRange = False
while askingAboutRange:
prompt_ch = self.stdscr.getch()
if chr(prompt_ch) in ['y', 'Y']: # yes, all range
self.undo.push()
self.deleteSegment([firstLineNum, firstColNum], height, width, frange=self.appState.playbackRange)
askingAboutRange = False
if chr(prompt_ch) in ['n', 'N']: # yes, all range
self.undo.push()
self.deleteSegment([firstLineNum, firstColNum], height, width)
askingAboutRange = False
elif prompt_ch == 27: # esc, cancel
askingAboutRange = False
prompting = False
elif chr(prompt_ch) in ['l', 'L']: # color
self.clearStatusBar()
if self.mov.hasMultipleFrames():
self.promptPrint("Color across all frames in playback range (Y/N)? ")
askingAboutRange = True
else:
self.undo.push()
self.colorSegment([firstLineNum, firstColNum], height, width)
askingAboutRange = False
while askingAboutRange:
prompt_ch = self.stdscr.getch()
if chr(prompt_ch) in ['y', 'Y']: # yes, all range
self.undo.push()
self.colorSegment([firstLineNum, firstColNum], height, width, frange=self.appState.playbackRange)
askingAboutRange = False
if chr(prompt_ch) in ['n', 'N']: # yes, all range
self.undo.push()
self.colorSegment([firstLineNum, firstColNum], height, width)
askingAboutRange = False
elif prompt_ch == 27: # esc, cancel
askingAboutRange = False
prompting = False
elif chr(prompt_ch) in ['f', 'F']: # fill
self.clearStatusBar()
self.promptPrint(f"Enter fill character, or press enter for {self.appState.drawChar}: ")
askingAboutChar = True
canceled = False
drawChar = 'X'
prompt_ch = self.stdscr.getch()
if prompt_ch == 27: # esc, cancel
canceled = True
elif prompt_ch in [13, curses.KEY_ENTER]:
drawChar = self.appState.drawChar
elif prompt_ch in [curses.KEY_F1]:
drawChar = chr(self.chMap['f1'])
elif prompt_ch in [curses.KEY_F2]:
drawChar = chr(self.chMap['f2'])
elif prompt_ch in [curses.KEY_F3]:
drawChar = chr(self.chMap['f3'])
elif prompt_ch in [curses.KEY_F4]:
drawChar = chr(self.chMap['f4'])
elif prompt_ch in [curses.KEY_F5]:
drawChar = chr(self.chMap['f5'])
elif prompt_ch in [curses.KEY_F6]:
drawChar = chr(self.chMap['f6'])
elif prompt_ch in [curses.KEY_F7]:
drawChar = chr(self.chMap['f7'])
elif prompt_ch in [curses.KEY_F8]:
drawChar = chr(self.chMap['f8'])
elif prompt_ch in [curses.KEY_F9]:
drawChar = chr(self.chMap['f9'])
elif prompt_ch in [curses.KEY_F10]:
drawChar = chr(self.chMap['f10'])
else:
drawChar = chr(prompt_ch)
if canceled:
askingAboutRange = False
prompting = False
else:
self.clearStatusBar()
if self.mov.hasMultipleFrames():
self.promptPrint("Fill across all frames in playback range (Y/N)? ")
askingAboutRange = True
else: # Just one frame, so don't worry about the range.
self.undo.push()
self.fillSegment([firstLineNum, firstColNum], height, width, fillChar=drawChar)
askingAboutRange = False
while askingAboutRange:
prompt_ch = self.stdscr.getch()
if chr(prompt_ch) in ['y', 'Y']: # yes, all range
self.undo.push()
self.fillSegment([firstLineNum, firstColNum], height, width, frange=self.appState.playbackRange, fillChar=drawChar)
askingAboutRange = False
if chr(prompt_ch) in ['n', 'N']: # yes, all range
self.undo.push()
self.fillSegment([firstLineNum, firstColNum], height, width, fillChar=drawChar)
askingAboutRange = False
elif prompt_ch == 27: # esc, cancel
askingAboutRange = False
prompting = False
elif chr(prompt_ch) in ['a', 'A']: # copy to all frames
self.copySegmentToAllFrames([firstLineNum, firstColNum], height, width, frange=self.appState.playbackRange)
prompting = False
elif prompt_ch == 27: # esc, cancel
self.undo.undo()
prompting = False
elif prompt_ch in [13, curses.KEY_ENTER]: # enter
# Confirm. Don't pop the clipboard like esc does.
prompting = False
selecting = False
elif c == curses.KEY_MOUSE:
try:
_, mouseX, mouseY, _, mouseState = curses.getmouse()
except:
pass
realmaxY,realmaxX = self.realstdscr.getmaxyx()
# enable mouse tracking only when the button is pressed
if not self.appState.hasMouseScroll:
curses.BUTTON5_PRESSED = 0
curses.BUTTON4_PRESSED = 0
try:
curses.BUTTON5_PRESSED
except:
curses.BUTTON5_PRESSED = 0
try:
curses.BUTTON4_PRESSED
except:
curses.BUTTON4_PRESSED = 0
if mouseState & curses.BUTTON4_PRESSED: # wheel up
if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX \
and mouseY + self.appState.topLine < self.appState.topLine + self.statusBarLineNum:
# We're in in edit/canvas area
self.move_cursor_up()
elif mouseState & curses.BUTTON5_PRESSED: # wheel down
if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX \
and mouseY + self.appState.topLine < self.appState.topLine + self.statusBarLineNum:
# We're in in edit/canvas area
self.move_cursor_down()
if mouseState == curses.BUTTON1_CLICKED or mouseState & curses.BUTTON_SHIFT:
if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX \
and mouseY + self.appState.topLine < self.appState.topLine + self.statusBarLineNum:
# We're in in edit/canvas area
self.xy[1] = mouseX + 1 + self.appState.firstCol # set cursor position
self.xy[0] = mouseY + self.appState.topLine
elif c == 27: # esc
selecting = False
if self.playing:
self.stdscr.nodelay(1)
else:
self.stdscr.nodelay(0)
def askHowToPaste(self):
self.clearStatusBar()
if self.mov.hasMultipleFrames():
self.promptPrint("Paste across all frames in playback range (Y/N)? ")
askingAboutRange = True
else: # only one frame
self.undo.push()
self.pasteFromClipboard()
askingAboutRange = False
while askingAboutRange:
prompt_ch = self.stdscr.getch()
if chr(prompt_ch) in ['y', 'Y']: # yes, all range
self.undo.push()
self.pasteFromClipboard(frange=self.appState.playbackRange)
askingAboutRange = False
if chr(prompt_ch) in ['n', 'N']: # no, single frame only
self.undo.push()
self.pasteFromClipboard()
askingAboutRange = False
elif prompt_ch == 27: # esc, cancel
askingAboutRange = False
prompting = False
def pasteFromClipboard(self, startPoint=None, clipBuffer=None, frange=None, transparent=False, pushUndo=True):
if not clipBuffer:
clipBuffer = self.clipBoard
if not clipBuffer: # clipboard is empty, and no buffer provided
return False
if pushUndo:
self.undo.push()
if not startPoint:
startPoint = self.xy
lineNum = 0
colNum = 0
#width = len(clipBuffer.content) - 1
width = len(clipBuffer.content)
#height = len(clipBuffer.content[0]) - 1
height = len(clipBuffer.content[0])
for lineNum in range(0, height):
for colNum in range(0, width):
charColumn = startPoint[1] + colNum
charLine = startPoint[0] + lineNum
character = ord(clipBuffer.content[colNum][lineNum])
cursesColorPair = clipBuffer.newColorMap[colNum][lineNum]
charFg = cursesColorPair[0]
charBg = cursesColorPair[1]
if charColumn < self.mov.sizeX + 1 and charLine < self.mov.sizeY:
if not frange:
if transparent:
if chr(character) == ' ' and charBg == 0:
pass
else:
self.insertChar(character, fg=charFg, bg=charBg, x=charColumn, y=charLine, pushUndo=False)
else:
self.insertChar(character, fg=charFg, bg=charBg, x=charColumn, y=charLine, pushUndo=False)
else:
if transparent:
if chr(character) == ' ' and charBg == 0:
pass
else:
self.insertChar(character, fg=charFg, bg=charBg, x=charColumn, y=charLine, pushUndo=False, frange=frange)
else:
self.insertChar(character, fg=charFg, bg=charBg, x=charColumn, y=charLine, pushUndo=False, frange=frange)
def copySegmentToClipboard(self, startPoint, height, width):
""" startPoint is [line, column] """
clipBoard = self.copySegmentToBuffer(startPoint, height, width)
self.clipBoard = clipBoard
def copySegmentToBrush(self, startPoint, height, width):
""" startPoint is [line, column] """
newBrush = self.copySegmentToBuffer(startPoint, height, width)
self.appState.brush = newBrush
def copySegmentToAllFrames(self, startPoint, height, width, frange=None):
self.undo.push()
tempFrame = self.copySegmentToBuffer(startPoint, height, width)
# paste into each frame in range, at startPoint
self.pasteFromClipboard(clipBuffer=tempFrame, startPoint=startPoint, frange=frange)
def copySegmentToBuffer(self, startPoint, height, width):
# Return a buffer, aka a frame or movie object
# Buffer can be put into clipboard, or used to move
# around screen, e, etc.
firstLineNum = startPoint[0]
firstColNum = startPoint[1]
lastLineNum = firstLineNum + height
lastColNum = firstColNum + width
# Make a buffer for characters and color pairs big enough to store the
# copied image segment.
# This can be done by making a frame.
bufferFrame = durmovie.Frame(height, width)
newLineNum = 0
newColNum = 0
# For each character in the selection...
for linenum in range(firstLineNum, lastLineNum):
for colnum in range(firstColNum - 1, lastColNum - 1):
# copy color from movie into buffer
charColor = self.mov.currentFrame.newColorMap[linenum][colnum]
try:
bufferFrame.newColorMap[newColNum][newLineNum] = charColor
except Exception as E:
print(f"Exception: {str(E)}")
pdb.set_trace()
# copy character from movie into buffer
output_char = self.mov.currentFrame.content[linenum][colnum]
try:
bufferFrame.content[newColNum][newLineNum] = output_char
except Exception as E:
print(f"Exception: {str(E)}")
pdb.set_trace()
newColNum += 1
newLineNum += 1
newColNum = 0
return bufferFrame
def flipSegmentVertical(self, startPoint, height, width, frange=None):
""" Flip the contents horizontally in the current frame, or framge range """
#self.undo.push()
# make a reverse copy
segment = self.copySegmentToBuffer(startPoint, height, width)
segment.flip_vertical()
# replace with our copy
self.pasteFromClipboard(clipBuffer = segment, frange=frange, startPoint=startPoint)
#self.pasteFromClipboard(frange=self.appState.playbackRange)
def flipSegmentHorizontal(self, startPoint, height, width, frange=None):
""" Flip the contents horizontally in the current frame, or framge range """
#self.undo.push()
# make a reverse copy
segment = self.copySegmentToBuffer(startPoint, height, width)
segment.flip_horizontal()
# replace with our copy
self.pasteFromClipboard(clipBuffer = segment, frange=frange, startPoint=startPoint)
#self.pasteFromClipboard(frange=self.appState.playbackRange)
def deleteSegment(self, startPoint, height, width, frange=None):
""" Delete everyting in the current frame, or framge range """
self.undo.push()
for lineNum in range(0, height):
for colNum in range(0, width):
charColumn = startPoint[1] + colNum
charLine = startPoint[0] + lineNum
character = ord(" ")
charFg = self.appState.defaultFgColor
charBg = self.appState.defaultBgColor
if charColumn < self.mov.sizeX + 1 and charLine < self.mov.sizeY:
if not frange:
self.insertChar(character, fg=charFg, bg=charBg, x=charColumn, y=charLine, pushUndo=False)
else:
self.insertChar(character, fg=charFg, bg=charBg, x=charColumn, y=charLine, pushUndo=False, frange=frange)
def fillSegment(self, startPoint, height, width, frange=None, fillChar="X"):
""" Fill everyting in the current frame, or framge range, with selected character+color """
self.undo.push()
for lineNum in range(0, height):
for colNum in range(0, width):
charColumn = startPoint[1] + colNum
charLine = startPoint[0] + lineNum
character = ord(fillChar)
charFg = self.colorfg
charBg = self.colorbg
if charColumn < self.mov.sizeX + 1 and charLine < self.mov.sizeY:
if not frange:
self.insertChar(character, fg=charFg, bg=charBg, x=charColumn, y=charLine, pushUndo=False)
else:
self.insertChar(character, fg=charFg, bg=charBg, x=charColumn, y=charLine, pushUndo=False, frange=frange)
def colorSegment(self, startPoint, height, width, frange=None):
""" Color everyting in the current frame, or framge range, with selected color """
self.undo.push()
for lineNum in range(0, height):
for colNum in range(0, width):
charColumn = startPoint[1] + colNum
charLine = startPoint[0] + lineNum
if charColumn < self.mov.sizeX + 1 and charLine < self.mov.sizeY:
if not frange:
#self.insertChar(character, fg=charFg, bg=charBg, x=charColumn, y=charLine, pushUndo=False)
self.insertColor(fg=self.colorfg, bg=self.colorbg, x=charColumn, y=charLine, pushUndo=False)
else:
#self.insertChar(character, fg=charFg, bg=charBg, x=charColumn, y=charLine, pushUndo=False, frange=frange)
self.insertColor(fg=self.colorfg, bg=self.colorbg, x=charColumn, y=charLine, pushUndo=False, frange=frange)
def clearStatusBarNoRefresh(self):
self.addstr(self.statusBarLineNum, 0, " " * self.mov.sizeX) # clear lower status bar
self.addstr(self.statusBarLineNum + 1, 0, " " * self.mov.sizeX) # clear upper status bar
def clearStatusBar(self):
self.addstr(self.statusBarLineNum, 0, " " * self.mov.sizeX) # clear lower status bar
self.addstr(self.statusBarLineNum + 1, 0, " " * self.mov.sizeX) # clear upper status bar
self.refresh()
def parseArgs(self):
""" do argparse stuff, get filename from user """
pass
durdraw-0.28.0/durdraw/durdraw_ui_widgets.py 0000664 0000000 0000000 00000073010 14704761225 0021177 0 ustar 00root root 0000000 0000000 # Stuff to draw widgets, status bars, etc. The logical parts, separate from
# the drawing/ncurses part.
# For example, in the status bar, we have a color swatches widget.
# The color swatch widget should know everything about itself except curses.
# NO CURSES CODE IN THIS FILE!!!!
# Instead make a durdraw_ui_widgets_curses that handles the curses bits. Then do the same with kivy, etc.
# # it should know its location inside of the Status Bar
import pdb
import time
from durdraw.durdraw_ui_widgets_curses import ButtonHandler
from durdraw.durdraw_ui_widgets_curses import ColorPickerHandler
from durdraw.durdraw_ui_widgets_curses import ColorSwatchHandler
from durdraw.durdraw_ui_widgets_curses import DrawCharPickerHandler
from durdraw.durdraw_ui_widgets_curses import MenuHandler
from durdraw.durdraw_ui_widgets_curses import StatusBarHandler
from durdraw.durdraw_ui_widgets_curses import FgBgColorPickerHandler
from durdraw.durdraw_ui_widgets_curses import ToolTipHandler
from durdraw.durdraw_gui_manager import Gui
class Button():
def __init__(self, label, x, y, callback, window, invisible = False, appState=None):
#self.location = 0; # 0 = beginning of /tring
self.x :int = x
self.y :int = y
self.appState = appState
self.realX :int = x
self.realY :int = y
self.label :str = label # What you should click
self.identity :str = label # Unique from the label
self.tooltip_command :str = None
self.tooltip_hidden :bool = True
self.persistant_tooltip :bool = False
self.width :int = len(self.label)
self.color :str = "brightGreen" # bright green = clickable by defaulta
self.image = None # If we want an icon in a GUI version
self.window = window # stdscr in the case of screen
self.callback = callback
# sub_buttons automatically show and hide with this
# button. like satellites.
#
# {my-label: sub-button-pointer}
# The idea is that when the label is "Draw" and I run .show(),
# call sub-button.show().
self.sub_buttons = {}
self.parameter = None
self.hidden :bool = False
self.enabled = True # Responds to clicks
self.selected :bool = False
self.picker :bool = False
self.invisible :bool = invisible # If true, responds to clicks but does not show. Useful for "overlays"
self.handler = ButtonHandler(self, self.window, callback, appState=self.appState)
def add_sub_button(self, label, button):
""" Label is what self.label should be when I should
calll button.show() """
self.sub_buttons.update({label: button})
def set_tooltip_command(self, command: str):
""" Command should be the keyboard command, like the "o" in esc-o """
self.tooltip_command = command
def get_tooltip_command(self):
return self.tooltip_command
def set_label(self, label):
self.label = label
self.width = len(self.label)
def hide(self):
self.hidden = True
#self.handler.hidden = True
for label in self.sub_buttons:
self.sub_buttons[label].hide()
self.handler.hide()
def show(self):
self.hidden = False
for label in self.sub_buttons:
if self.label == label:
self.sub_buttons[label].show()
self.draw()
def update_real_xy(self, x=None, y=None):
if x == None:
x = self.realX
if y == None:
y = self.realY
self.realX = x
self.realY = y
def draw(self):
should_draw = True
if self.invisible:
should_draw = False
if self.hidden:
should_draw = False
if self.appState.playOnlyMode:
should_draw = False
if should_draw:
self.handler.draw()
def make_invisible(self):
self.invisible = True
def become_selected(self):
self.selected = True
self.handler.draw()
self.selected = False
def on_click(self):
#result = self.do_nothing()
result = None
#if self.hidden == False:
if self.enabled and not self.hidden:
self.selected = True
self.handler.draw()
result = self.handler.on_click()
self.selected = False
return result
def do_nothing(self):
pass
def handle_event(self, event):
return self.handler.handle_event(event)
class Menu():
def __init__(self, window, x=0, y=0, caller=None, appState=None, statusBar=None):
""" init menu items """
self.window = window
self.caller = caller
self.appState=appState
self.items = {}
self.buttons = []
self.hidden = True
self.title = None
self.statusBar = None
self.is_submenu = False
self.x = x
self.y = y
self.handler = MenuHandler(self, window, appState=appState)
self.gui = Gui(window=self.window)
def set_x(self, x):
self.x = x
self.handler.x = x
def set_y(self, y):
self.y = y
self.handler.y = y
def set_title(self, title):
self.title = title
self.handler.title = self.title
def add_item(self, label, on_click, hotkey, shortcut=None, has_submenu=False):
props = {"on_click": on_click, "hotkey": hotkey, "shortcut": shortcut, "has_submenu": has_submenu}
item = {label: props}
self.items.update(item)
# add button
#itemButton = Button(label, 0, 0, on_click, self.window)
if shortcut:
long_label = f"{label} {shortcut}"
else:
long_label = f"{label} poop"
itemButton = Button(long_label, 0, self.x, on_click, self.window, appState=self.appState)
itemButton.make_invisible()
self.buttons.append(itemButton)
#self.handler.rebuild()
#itemButton.update_real_xy(x=self.caller.x)
def show(self):
for button in self.buttons:
#button.x = self.x
#button.update_real_xy(x=self.x)
self.gui.add_button(button)
button.show()
#pdb.set_trace()
self.hidden = False
response = self.handler.show()
return response
def tryHotKey(self, key):
""" Try a hotkey to see if it works """
pass
def hide(self):
self.handler.hide()
for button in self.buttons:
self.gui.del_button(button)
self.hidden = True
def showHide(self):
if self.hidden == True:
return self.show()
else:
self.hide()
class FgBgColorPicker:
""" Draw the FG and BG color boxes that you can click on to launch a
color selector """
def __init__(self, window, x=0, y=0):
self.window = window
self.x = x
self.y = y
self.handler = FgBgColorPickerHandler(self, x=self.x, y=self.y)
def draw(self):
self.handler.draw()
class DrawCharPicker:
""" Ask the user for the character to draw with """
def __init__(self, window, x=0, y=0, caller=None):
self.window = window
self.caller = caller
self.appState = self.caller.caller.appState
self.handler = DrawCharPickerHandler(self, self.window)
def pickChar(self):
self.handler.pickChar()
class ColorPicker:
""" Draw a color palette, let the user click a color.
Makes the user's selected color available somewhere. """
def __init__(self, window, x=0, y=0, caller=None, colorMode="256", type="fg"):
self.hidden = True
self.window = window
self.colorMap = {}
self.colorMode = colorMode
self.type = type # "fg" or "bg," or maybe "fgbg"
self.x = x
self.y = y
self.totalColors = 256
self.appState = caller.appState
if colorMode == "256":
self.height = 8
self.width = 38
self.totalColors = 255
elif colorMode == "16":
#self.height = 1
#self.width = 16
# short and wide - good
self.height = 2
self.width = 10
# tall and thin - good, but color order
# is wrong
#self.height = 10
#self.width = 4
self.totalColors = 16
if self.appState.iceColors:
self.totalColors = 15
self.caller = caller
self.handler = ColorPickerHandler(self, window, width=self.width, height=self.height)
def showHide(self):
#pdb.set_trace()
if self.hidden == True:
self.hidden = False
self.caller.appState.colorPickerSelected = True
self.show()
elif self.hidden == False:
self.hidden = True
self.caller.appState.colorPickerSelected = False
self.hide()
def switchTo(self):
""" Switch user interaction to the color picker,
already on the screen """
self.caller.appState.colorPickerSelected = True
self.showFgPicker()
def show(self):
self.hidden = False
#self.showFgPicker()
self.handler.show()
def showFgPicker(self, message=None):
""" Returns the color picked by the user
"""
# Draw foreground colors 1-5 in a panel.
# Run an input loop to let user use mouse or keyboard
# to select color (arrows and return). Then return the
# selected color.
self.hidden = False
color = self.handler.showFgPicker(message=message)
if not self.caller.appState.sideBarShowing:
self.hide()
return color
def hide(self):
self.hidden = True
self.handler.hide()
class MiniSelector(): # for seleting cursor mode
""" Line up some characters beside each other, and let the user select
which one by clicking on it. The currently selected one is drawn inverse.
Example, for picking between Select, Draw and Color: SPC """
def __init__(self):
self.items = []
class ColorSwatch():
def __init__(self, caller, x=0, y=0):
""" Initialize a swatch of 24 colors
"""
window = caller.window
self.window = window
self.handler = ColorSwatchHandler(self, self.window)
self.bank = []
self.colorMap = {}
self.x = x
self.y = y
for color in range(0,24):
self.bank.append(color)
#swatch = [1] * 24 # color 1
def draw(self):
self.handler.draw()
class ToolTipsGroup():
""" These are tooltips that can show up without being
tied to any specific object, other than an x/y location """
def __init__(self, caller):
self.tips = []
self.hidden = True
self.caller = caller
def add_tip(self, hotkey :str, row=0, col=0):
newTip = ToolTip(self.caller)
newTip.hotkey = hotkey
newTip.row = row
newTip.column = col
self.tips.append(newTip)
def get_tip(self, hotkey :str):
for tip in self.tips:
if tip.hotkey == hotkey:
return tip
return False
def show(self):
self.hidden = False
for tip in self.tips:
tip.show()
def hide(self):
self.hidden = True
for tip in self.tips:
tip.hide()
class ToolTip():
def __init__(self, context):
# context is the caller, basically the statusbar
# context provides window, appState, etc
self.hotkey = '' # the key the user presses
self.column = 0 # on the screen
self.row = 0
self.hidden = True
self.alwaysHidden = False
self.handler = ToolTipHandler(self, context)
def set_hotkey(self, hotkey :str):
self.hotkey = hotkey
def set_location(self, row=None, column=None):
if row == None:
row = self.row
if column == None:
column = self.column
self.row = row
self.column = column
def show(self):
if not self.alwaysHidden:
self.hidden = False
self.handler.show()
def hide(self):
self.hidden = True
self.handler.hide()
class StatusBar():
def __init__(self, caller, x=0, y=0, appState=None):
window = caller.stdscr
self.caller=caller
self.appState = appState
self.window = window
self.gui = caller.gui # top level gui handler thing
self.handler = StatusBarHandler(self, window)
self.items = []
self.buttons = []
self.colorPickerEnabled = False
self.hidden = False
self.x = x
self.y = y
# Initialize tooltips that aren't tied to a button object
# These show up when the user hits esc, along with the
# visible buttons' tooltips.
# "Free floating tips"
self.other_tooltips = ToolTipsGroup(self)
self.other_tooltips.add_tip("g", row=0, col=7) # Frame
self.other_tooltips.add_tip("+", row=0, col=7) # FPS+
self.other_tooltips.add_tip("-", row=0, col=7) # FPS-
self.other_tooltips.add_tip("D", row=0, col=7) # Frame delay
self.other_tooltips.add_tip("R", row=0, col=7) # Frame range
self.other_tooltips.add_tip("c", row=0, col=7) # color picker
self.other_tooltips.add_tip("[", row=0, col=7) # prev charset
self.other_tooltips.add_tip("]", row=0, col=7) # next charset
self.other_tooltips.add_tip("p", row=0, col=7) # play/pause
self.other_tooltips.add_tip("j", row=0, col=7) # prev frame
self.other_tooltips.add_tip("k", row=0, col=7) # next frame
# If we're in 16 color mode, always hide the "c" button
if self.appState.colorMode == "16":
colorPicker_tooltip = self.other_tooltips.get_tip('c')
colorPicker_tooltip.alwaysHidden = True
# Settings menu
#settingsMenuColumn = mainMenu.handler.width # Try to place to the right of the main menu
settingsMenuColumn = 24 # Try to place to the right of the main menu
settingsMenu = Menu(self.window, x = self.x - 2, y = settingsMenuColumn, caller=self, appState=self.appState, statusBar=self)
settingsMenu.add_item("16 Color Mode", caller.switchTo16ColorMode, "1")
settingsMenu.add_item("256 Color Mode", caller.switchTo256ColorMode, "2")
settingsMenu.add_item("VGA Colors", caller.enableTrueCGAColors, "v")
settingsMenu.add_item("ZX Spectrum Colors", caller.enableTrueSpeccyColors, "z")
settingsMenu.add_item("C64 Colors", caller.enableTrueC64Colors, "c")
#settingsMenu.add_item("Deafult Colors", caller.resetColorsToDefault, "d")
settingsMenu.add_item("Toggle Mouse", caller.toggleMouse, "m")
settingsMenu.add_item("Toggle Color Scroll", caller.toggleColorScrolling, "s")
settingsMenu.add_item("Toggle Wide Wrapping", caller.toggleWideWrapping, "w")
if self.appState.mental: # Experimental stuff
settingsMenu.add_item("Toggle iCE Colors (MENTAL)", caller.toggleIceColors, "i")
settingsMenu.add_item("Toggle Injecting (MENTAL)", caller.toggleInjecting, "j")
if self.appState.debug:
settingsMenu.add_item("Toggle Debug", caller.toggleDebug, "d")
settingsMenu.add_item("Python Console", caller.jumpToPythonConsole, "p")
settingsMenu.is_submenu = True
#settingsMenu.add_item("Show/Hide Sidebar", caller.toggleSideBar, "s")
settingsMenu.set_x(self.x - 1)
settingsMenu.set_y(settingsMenuColumn)
self.settingsMenu = settingsMenu
# Transforms menu
#transformMenuColumn = 24 # Try to place to the right of the main menu
transformMenuColumn = 35 # Try to place to the right of the Animation menu
transformMenu = Menu(self.window, x = self.x - 2, y = transformMenuColumn, caller=self, appState=self.appState, statusBar=self)
transformMenu.add_item("Bounce", caller.transform_bounce, "b")
transformMenu.add_item("Repeat", caller.transform_repeat, "r")
transformMenu.add_item("Reverse", caller.transform_reverse, "v")
transformMenu.add_item("Apply NeoFetch Keys", caller.apply_neofetch_keys, "n")
#transformMenu.add_item("Show/Hide Sidebar", caller.toggleSideBar, "s")
transformMenu.set_x(self.x - 1)
transformMenu.set_y(transformMenuColumn)
transformMenu.is_submenu = True
self.transformMenu = transformMenu
# main menu items
self.menuButton = None
# Create a menu list item, add menu items to it
mainMenu = Menu(self.window, x = self.x - 1, y = self.y, caller=self, appState=self.appState, statusBar=self)
#mainMenu.gui = self.gui
mainMenu.add_item("New/Clear", caller.clearCanvasPrompt, "n", shortcut="esc-C")
mainMenu.add_item("Open", caller.openFromMenu, "o", shortcut="esc-o")
mainMenu.add_item("Save", caller.save, "s", shortcut="esc-s")
mainMenu.add_item("Undo", caller.clickedUndo, "u", shortcut="esc-z")
mainMenu.add_item("Redo", caller.clickedRedo, "r", shortcut="esc-r")
#mainMenu.add_item("16 Color Mode", caller.switchTo16ColorMode, "1")
#mainMenu.add_item("256 Color Mode", caller.switchTo256ColorMode, "2")
#mainMenu.add_item("Settings", settingsMenu.showHide, "t", has_submenu=True)
mainMenu.add_item("Character Sets", caller.showCharSetPicker, "c", shortcut="esc-S")
#mainMenu.add_item("Transform", caller.showTransformer, "a", has_submenu=True)
mainMenu.add_item("Info/Sauce", caller.clickedInfoButton, "i", shortcut="esc-i")
mainMenu.add_item("Color Picker", caller.selectColorPicker, "l", shortcut="tab")
mainMenu.add_item("Viewer Mode", caller.enterViewMode, "v", shortcut="esc-V")
mainMenu.add_item("Find /", caller.searchForStringPrompt, "/", shortcut="esc-F")
mainMenu.add_item("Replace Color", caller.replaceColorUnderCursor, "e", shortcut="esc-L")
mainMenu.add_item("Settings", caller.openSettingsMenu, "t", has_submenu=True)
mainMenu.add_item("Help", caller.showHelp, "h", shortcut="esc-h")
mainMenu.add_item("Quit", caller.safeQuit, "q", shortcut="esc-q")
#menuButton = Button("?", 0, 0, mainMenu.showHide, self.window)
#menuButton = Button("Menu", 0, 0, mainMenu.showHide, self.window, appState=self.appState)
menuButton = Button("Menu", 0, 0, caller.openMainMenu, self.window, appState=self.appState)
menuButton.set_tooltip_command('m')
self.menuButton = menuButton
menuButton.realX = self.x + menuButton.x
menuButton.realY = self.y + menuButton.y
menuButton.show()
self.menuButton = menuButton
#mainMenu.x = menuButton.realX - 1
#mainMenu.y = menuButton.realY
mainMenu.set_x(menuButton.realX - 1)
mainMenu.set_y(menuButton.realY)
self.mainMenu = mainMenu
# Animation menu
self.animButton = None
#animButton_offset = 18
animButton_offset = 7
# Create a menu list item, add menu items to it
animMenu = Menu(self.window, x = animButton_offset, y = self.y, caller=self, appState=self.appState, statusBar=self)
animMenu.set_title("Animation:")
animMenu.add_item("Clone Frame", caller.cloneToNewFrame, "n", shortcut="esc-n")
animMenu.add_item("Append Empty Frame", caller.appendEmptyFrame, "a", shortcut="esc-N")
animMenu.add_item("Delete Frame", caller.deleteCurrentFrame, "d", shortcut="esc-d")
animMenu.add_item("Set Frame Delay", caller.getDelayValue, "l", shortcut="esc-D")
animMenu.add_item("Set Playback Range", caller.getPlaybackRange, "r", shortcut="esc-R")
animMenu.add_item("Go to Frame", caller.gotoFrameGetInput, "g", shortcut="esc-g")
animMenu.add_item("Move Frame", caller.moveCurrentFrame, "m", shortcut="esc-M")
animMenu.add_item("Shift Frames Right", caller.shiftMovieRight, "}", shortcut="esc-}")
animMenu.add_item("Shift Frames Left", caller.shiftMovieLeft, "{", shortcut="esc-{")
animMenu.add_item("Transform", caller.openTransformMenu, "t", has_submenu=True)
animButton = Button("Anim", 0, animButton_offset, caller.openAnimMenu, self.window, appState=self.appState)
animButton.set_tooltip_command('a')
self.animButton = animButton
animButton.realX = self.x + animButton.x
animButton.realY = self.y + animButton.y
animButton.show()
self.animButton = animButton
animMenu.set_x(animButton.realX - 1)
animMenu.set_y(animButton.realY)
self.animMenu = animMenu
# Mouse tools menu
toolMenu = Menu(self.window, x=45, y=self.y, caller=self, appState=self.appState, statusBar=self)
toolMenu.set_title("Mouse Tools:")
#toolMenu = Menu(self.window, x=5, y=self.y, caller=self)
toolMenu.add_item("Move", self.setCursorModeMove, "m")
#toolMenu.add_item("Select", self.setCursorModeSelect, "s")
toolMenu.add_item("Draw", self.setCursorModeDraw, "d")
toolMenu.add_item("Paint", caller.setCursorModePaint, "p")
toolMenu.add_item("Color", self.setCursorModeCol, "c")
toolMenu.add_item("Erase", self.setCursorModeErase, "e")
toolMenu.add_item("Eyedrop", self.setCursorModeEyedrop, "y")
toolMenu.add_item("Draw/Fill Char", caller.openDrawCharPicker, "h")
self.toolMenu = toolMenu
# Make cursor tool selector button
# offset is how far right to put the button in the statusbar:
#toolButton_offset = 45
#toolButton_offset = 7
toolButton_offset = 14
#toolButton = Button("Tool", 0, toolButton_offset, toolMenu.showHide, self.window, appState=self.appState)
toolButton = Button("Tool", 0, toolButton_offset, caller.openMouseToolsMenu, self.window, appState=self.appState)
#toolButton = Button("Tool", 0, 5, toolMenu.showHide, self.window)
#toolButton.label = self.caller.appState.cursorMode
toolButton.set_label(self.caller.appState.cursorMode)
toolButton.set_tooltip_command('t')
toolButton.picker = True
toolButton.realX = self.x + toolButton.x # toolbar shit
toolButton.realY = self.y + toolButton.y
toolButton.show()
toolMenu.set_x(toolButton.realX - 1) # line up the menu above the button
toolMenu.set_y(toolButton.realY)
self.toolButton = toolButton
# This thing is already in the menu. Maybe we should reclaim
# the real estate from the status bar.
if self.caller.appState.showCharSetButton:
charSetButton = Button("CharSet", 1, 26, caller.showCharSetPicker, self.window, appState=self.appState)
# make proper label for character set button
charSetLabel = self.caller.appState.characterSet
charSetButton.set_tooltip_command('S')
if charSetLabel == "Unicode Block":
charSetLabel = self.caller.appState.unicodeBlock
charSetLabel = f"{charSetLabel[:3]}.."
charSetButton.set_label(charSetLabel)
if self.caller.appState.colorMode == "16":
charSetButton.hide()
self.charSetButton = charSetButton
charSetButton.hide()
# Brush picker - make me a real brush someday.
drawCharPicker_offset = toolButton_offset + 6 # to the right of Draw menu
drawCharPicker_offset += 4 # accomodate for eyedrop for now. yes, this is dumb
drawCharPicker = DrawCharPicker(self.window, caller=self)
drawCharPickerButton = Button(self.caller.appState.drawChar, 0, drawCharPicker_offset, drawCharPicker.pickChar, self.window, appState=self.appState)
drawCharPickerButton.picker = True
drawCharPickerButton.identity = "drawChar"
drawCharPickerButton.realX = self.x + drawCharPickerButton.x # toolbar shit
drawCharPickerButton.realY = self.y + drawCharPickerButton.y
drawCharPickerButton.show()
#drawCharPickerButton.hide()
self.drawCharPickerButton = drawCharPickerButton
self.drawCharPicker = drawCharPicker
# This is to make the char picker button hide/show when
# toolButton's label is set to "Draw"
self.toolButton.add_sub_button("Draw", drawCharPickerButton)
#colorPicker = ColorPicker(self.window, x=self.x - 2, y = self.y + 2, caller=caller)
colorPicker = ColorPicker(self.window, x=self.x - 7, y = self.y + 2, caller=caller)
self.colorPicker_256 = colorPicker
self.colorPicker = self.colorPicker_256
#self.colorPickerButton = Button("FG: ", 1, 0, colorPicker.showHide, self.window, appState=self.appState)
self.colorPickerButton = Button("FG: ", 1, 0, colorPicker.switchTo, self.window, appState=self.appState)
self.colorPickerButton.invisible = True
self.colorPickerButton.persistant_tooltip = True
self.colorPickerButton.set_tooltip_command('c')
self.colorPickerButton.realX = self.x + self.colorPickerButton.x
self.colorPickerButton.realY = self.y + self.colorPickerButton.y
self.items.append(self.colorPickerButton)
self.buttons.append(self.colorPickerButton)
self.colorPickerButton.show()
#if self.caller.appState.colorMode == "256":
# self.colorPickerButton.show()
#else:
# self.colorPickerButton.hide()
colorPicker_16 = ColorPicker(self.window, x=self.x - 7, y = self.y + 2, caller=caller, colorMode="16")
self.colorPicker_16 = colorPicker_16
#colorPicker_bg_16 = ColorPicker(self.window, x=self.x - 7, y = self.y + 2, caller=caller, colorMode="16", type="bg")
#self.colorPicker_bg_16 = colorPicker_bg_16
#pdb.set_trace()
#colorPicker.show() # for testing
#self.swatch = ColorSwatch(self, x=3, y=self.y)
#self.swatch.colorMap = caller.ansi.colorPairMap
# Figure out where in the status bar to put it
newX = len(str(self.items))
newY = self.y
#fgBgColors = FgBgColorPicker(self.window, x=newX, y=newY)
#menuButton.addItem(label="Help!", type="link", callback=None)
# Initialize individual buttons and items
#startButton = Button(label="!", callback=self.draw_start_menu)
self.items.append(menuButton)
self.items.append(toolButton)
self.items.append(drawCharPickerButton)
self.items.append(animButton)
if self.appState.colorMode != '16': # in 16 color mode, don't cover up the bg color picker
if self.caller.appState.showCharSetButton:
self.items.append(charSetButton)
#self.items.append(fgBgColors)
#self.items.append(self.swatch)
self.buttons.append(menuButton)
self.buttons.append(toolButton)
self.buttons.append(drawCharPickerButton)
self.buttons.append(animButton)
if self.caller.appState.showCharSetButton:
self.buttons.append(charSetButton)
# Add them to the items
def hide(self):
self.hidden = True
for item in self.items:
item.hide()
for item in self.buttons:
item.hide()
def show(self):
self.hidden = False
exclude_list = ["drawChar"] # button identities to exclude
for item in self.items:
if item.identity not in exclude_list:
item.show()
for item in self.buttons:
if item.identity not in exclude_list:
item.show()
def showToolTips(self):
for item in self.buttons:
item.handler.showToolTip()
self.other_tooltips.show()
def hideToolTips(self):
for item in self.buttons:
item.handler.hideToolTip()
def enableColorPicker(self):
pass
def disableColorPicker(self):
pass
def setCursorModeMove(self):
self.caller.appState.setCursorModeMove()
self.caller.disableMouseReporting()
self.toolButton.set_label(self.caller.appState.cursorMode)
#self.drawCharPickerButton.hide()
def setCursorModeSelect(self):
self.caller.appState.setCursorModeSelect()
self.caller.disableMouseReporting()
self.toolButton.set_label(self.caller.appState.cursorMode)
#self.drawCharPickerButton.hide()
def setCursorModeDraw(self):
self.caller.appState.setCursorModeDraw()
self.caller.enableMouseReporting()
self.toolButton.set_label(self.caller.appState.cursorMode)
#self.drawCharPickerButton.show()
def setCursorModePaint(self):
self.caller.appState.setCursorModePaint()
self.caller.enableMouseReporting()
self.toolButton.set_label(self.caller.appState.cursorMode)
def setCursorModeCol(self):
self.caller.appState.setCursorModeCol()
self.caller.disableMouseReporting()
self.toolButton.set_label(self.caller.appState.cursorMode)
#self.drawCharPickerButton.hide()
def setCursorModeErase(self):
self.caller.appState.setCursorModeErase()
self.caller.disableMouseReporting()
self.toolButton.set_label(self.caller.appState.cursorMode)
#self.drawCharPickerButton.hide()
def setCursorModeEyedrop(self):
self.caller.appState.setCursorModeEyedrop()
self.caller.disableMouseReporting()
#self.toolButton.set_label(self.caller.appState.cursorMode)
self.toolButton.set_label("Eye")
#self.drawCharPickerButton.hide()
def updateLocation(self, x, y):
self.x = x
self.y = y
def draw(self):
""" Draw the status bar """
if self.hidden:
return False
self.handler.draw()
for item in self.items:
if item.hidden is False:
item.handler.draw(plusX=self.x, plusY=self.y)
durdraw-0.28.0/durdraw/durdraw_ui_widgets_curses.py 0000664 0000000 0000000 00000134552 14704761225 0022574 0 ustar 00root root 0000000 0000000 # Handlers for durdraw_ui_widgets.py classes
import curses
import pdb
import time
import curses.panel
def curses_cursorOff():
try:
curses.curs_set(0) # turn off cursor
except curses.error:
pass # .. if terminal supports it.
def curses_cursorOn():
try:
curses.curs_set(1) # turn on cursor
except curses.error:
pass # .. if terminal supports it.
def curses_notify(screen, message, pause=False):
#screen.cursorOff()
#screen.clearStatusLine()
screen.addstr(0, 0, message)
screen.refresh()
if pause:
if screen.playing:
screen.nodelay(0) # wait for input when calling getch
screen.getch()
screen.nodelay(1) # do not wait for input when calling getch
else:
screen.getch()
if not pause:
curses.napms(1500)
curses.flushinp()
#screen.clearStatusLine()
#screen.cursorOn()
screen.refresh()
def curses_addstr(window, y, x, text, attr=None): # addstr(y, x, str[, attr]) and addstr(str[, attr])
""" Wraps curses addstr in a try;except, prevents addstr from
crashing cureses if it fails """
if not attr:
try:
window.addstr(y, x, text)
except curses.error as e:
curses_notify(window, f"Debug: Curses error in addstr(): {e.args[0]}")
#self.testWindowSize()
else:
try:
window.addstr(y, x, text, attr)
except curses.error:
pass # silent ugly fail
#curses_notify(window, f"Debug: Curses error in addstr(): {curses.error}")
#self.testWindowSize()
class MenuHandler:
""" hook into Curses to draw menu
"""
def __init__(self, menu, window, appState=None, statusBar=None):
self.menu = menu
self.window = window
self.appState=appState
self.parentWindow = self.menu.caller.window
self.x = menu.x
self.y = menu.y
self.width = None
self.height = None
self.title = menu.title # show the title if one is set
self.menuOriginLine = 0
#self.rebuild()
#self.panel = curses.panel.new_panel(self.curses_win)
def rebuild(self):
height = len(self.menu.items) + 2 # 2 for top and bottom border lines
if self.title:
height += 1
# find widest item in list, go a few characters larger
width = len(max(self.menu.items, key = len)) + 4 + 7 # 4 for padding and side borders, more for shortcuts
if self.menu.title:
titleWidth = len(self.menu.title) + 4
if titleWidth> width:
width = titleWidth
self.width = width
self.x = self.menu.x - height
self.curses_win = curses.newwin(height, width, self.x, self.y)
#self.curses_win.border()
line = 1
if self.title:
line += 1
textColor = curses.color_pair(self.appState.theme['mainColor']) | curses.A_BOLD
buttonColor = curses.color_pair(self.appState.theme['clickColor'])
shortcutColor = curses.color_pair(self.appState.theme['menuBorderColor'])
borderColor = curses.color_pair(self.appState.theme['menuBorderColor'])
menuTitleColor = curses.color_pair(self.appState.theme['menuTitleColor']) | curses.A_BOLD | curses.A_UNDERLINE
maxX, maxY = self.parentWindow.getmaxyx()
self.menuOriginLine = maxX - 2 - height
# Draw a pretty border
curses_addstr(self.curses_win, 0, 0, ".", borderColor)
curses_addstr(self.curses_win, 0, 1, ("." * (self.width - 2)), borderColor)
curses_addstr(self.curses_win, 0, width - 1, ".", borderColor)
if self.title:
curses_addstr(self.curses_win, 1, 0, ':', borderColor)
curses_addstr(self.curses_win, 1, 2, self.menu.title, menuTitleColor) # Menu title
curses_addstr(self.curses_win, 1, width - 1, ':', borderColor)
for (item, button) in zip(self.menu.items, self.menu.buttons):
shortcut = self.menu.items[item]["shortcut"]
has_submenu = self.menu.items[item]["has_submenu"]
curses_addstr(self.curses_win, line, 0, ':', borderColor)
curses_addstr(self.curses_win, line, width - 1, ':', borderColor)
curses_addstr(self.curses_win, line, 2, item, textColor) # Menu item
if shortcut:
curses_addstr(self.curses_win, line, width - 7, shortcut, shortcutColor)
if has_submenu:
curses_addstr(self.curses_win, line, width - 2, ">", shortcutColor)
top_of_menu = self.menu.caller.y - len(self.menu.buttons)
button.update_real_xy(x=self.menuOriginLine + line, y=self.menu.y) # working for putting menu on first line
button.window = self.window
line += 1
curses_addstr(self.curses_win, line, 0, ':', borderColor)
curses_addstr(self.curses_win, line, width - 1, ':', borderColor)
curses_addstr(self.curses_win, line, 1, ("." * (width - 2)), borderColor)
self.panel = curses.panel.new_panel(self.curses_win)
try:
self.panel.hide()
except:
pass
def show(self):
try:
self.rebuild()
self.panel.top()
#self.panel.move(0,0)
#self.panel.move(self.menuOriginLine, 0)
#self.panel.move(self.menuOriginLine, self.menu.x)
self.panel.move(self.menuOriginLine, self.menu.y)
self.panel.show()
except: # The window was probably too short, so panel.move() returns ERR.
curses_cursorOn()
self.menu.hide()
response = "Close" # default thing to do when done, returned to menu wrapper
return response
self.curses_win.keypad(True)
curses.panel.update_panels()
curses.doupdate()
self.curses_win.refresh()
# Input loop
self.window.nodelay(1)
#self.curses_win.nodelay(0)
prompting = True
options = []
current_option = 0
for item in self.menu.items:
options.append(item)
self.refresh()
curses_cursorOff()
#pdb.set_trace()
#curses.mousemask(1)
#print('\033[?1003l') # disable mouse reporting
borderColor = curses.color_pair(self.appState.theme['borderColor'])
menuItemColor = curses.color_pair(self.appState.theme['menuItemColor'])
response = "Close" # default thing to do when done, returned to menu wrapper
while(prompting):
time.sleep(0.01)
line = 1
if self.title:
line += 1
c = self.window.getch()
for item in self.menu.items:
if item == options[current_option]: # selected item
textColor = menuItemColor | curses.A_REVERSE
else:
textColor = menuItemColor
curses_addstr(self.curses_win, line, 2, item, textColor)
hotkeyIndex = 0
itemString = next(iter(item))
foundHotkey = False
for letter in item:
if letter.lower() == self.menu.items[item]["hotkey"] and foundHotkey == False:
curses_addstr(self.curses_win, line, 2 + hotkeyIndex, letter, textColor | curses.A_UNDERLINE | curses.A_BOLD)
foundHotkey = True
hotkeyIndex += 1
line += 1
curses.panel.update_panels()
self.window.refresh()
if c == ord(self.menu.items[item]["hotkey"]): # hotkey pressed
if self.menu.items[item]["has_submenu"]: # If it opens a sub-menu..
# Keep it on the screen.
# Redraw previously selected as normal:
if not self.menu.title:
curses_addstr(self.curses_win, current_option + 1, 2, options[current_option], menuItemColor)
else:
curses_addstr(self.curses_win, current_option + 2, 2, options[current_option], menuItemColor)
# Then highlight the new one.
current_option = options.index(item)
#self.rebuild()
textColor = menuItemColor | curses.A_REVERSE
curses_addstr(self.curses_win, line - 1, 2, item, textColor)
else:
# Otherwise, hide it
self.hide()
if not self.menu.caller.caller.playing: # caller.caller is the main UI thing
self.window.nodelay(0)
self.menu.items[item]["on_click"]()
prompting = False
if c == curses.KEY_UP:
current_option = max(0, current_option - 1)
#pdb.set_trace()
elif c == curses.KEY_DOWN:
current_option = min(len(options) - 1, current_option + 1)
elif c in [13, curses.KEY_ENTER]: # selected an option
#pdb.set_trace()
self.hide()
prompting = False
# yikes lol
self.menu.items[options[current_option]]["on_click"]()
self.appState.colorPickerSelected = False
#if not self.menu.caller.caller.playing: # caller.caller is the main UI thing
# self.window.nodelay(0)
elif c in [98, curses.KEY_LEFT]:
self.hide()
prompting = False
response = "Left"
if self.menu.is_submenu:
response = "Pop"
#self.hide()
#prompting = False
# Here: Launch a different menu
#self.menu.statusBar.menuButton.on_click
elif c in [102, curses.KEY_RIGHT]:
if self.menu.items[options[current_option]]["has_submenu"]: # If it opens a sub-menu..
#curses_notify(window, f"Debug: Fnord")
#self.hide()
#prompting = False
self.menu.items[options[current_option]]["on_click"]()
prompting = False
else:
# Jump out of the menu, and tell the parent handler to move to the next menu
self.hide()
prompting = False
response = "Right"
#self.hide()
#prompting = False
# Here: Launch a different menu
elif c == 27: # normal esc
self.hide()
prompting = False
elif c == curses.KEY_MOUSE:
try:
_, mouseX, mouseY, _, mouseState = curses.getmouse()
except:
pass
if not self.appState.hasMouseScroll:
curses.BUTTON5_PRESSED = 0
curses.BUTTON4_PRESSED = 0
if mouseState & curses.BUTTON4_PRESSED: # wheel up
current_option = max(0, current_option - 1)
elif mouseState & curses.BUTTON5_PRESSED: # wheel down
current_option = min(len(options) - 1, current_option + 1)
else: # assume a click
# Did the user click in the menu area?
if mouseY > self.menuOriginLine and mouseY < self.menuOriginLine + len(self.menu.items): # on a menu line?
if mouseX < self.x and mouseX > self.x - self.width: # in a menu column
# Un-highlight selected item
curses_addstr(self.curses_win, current_option + 1, 2, options[current_option], menuItemColor)
# Highlight the one we're clicking on
current_option = mouseY - self.menuOriginLine - 1
#item = self.menu.items[options[current_option]]
#curses_notify(self.window, f"current_option: {current_option}")
#self.rebuild()
textColor = menuItemColor | curses.A_REVERSE
curses_addstr(self.curses_win, current_option + 1, 2, options[current_option], textColor)
has_submenu = self.menu.items[options[current_option]]["has_submenu"]
if has_submenu:
prompting = False
self.menu.items[options[current_option]]["on_click"]()
else:
self.hide()
prompting = False
#self.menu.items[options[current_option]]["on_click"]()
if not self.menu.caller.caller.playing: # caller.caller is the main UI thing
self.window.nodelay(0)
self.menu.gui.got_click("Click", mouseX, mouseY)
else:
#curses_notify(self.window, f"Debug: mouseX: {mouseX}, mouseY: {mouseY}, self.x: {self.x}, self.menuOriginLine: {self.menuOriginLine}")
prompting = False
self.menu.gui.got_click("Click", mouseX, mouseY)
self.hide()
#curses_notify(self.window, f"This should never happen. mouseX: {mouseX}, mouseY: {mouseY}, self.x: {self.x}, self.menuOriginLine: {self.menuOriginLine}")
else:
#curses_notify(self.window, f"Debug: mouseX: {mouseX}, mouseY: {mouseY}, self.x: {self.x}, self.menuOriginLine: {self.menuOriginLine}")
prompting = False
self.menu.gui.got_click("Click", mouseX, mouseY)
self.hide()
#jif c in [104, 63]: # h H Help
# self.hide()
# self.items["Help"]["on_click"]()
# prompting = False
#pdb.set_trace()
curses_cursorOn()
self.menu.hide()
if not self.menu.caller.caller.playing: # lol .. the caller.caller is the main UI thing
self.window.nodelay(0)
#pdb.set_trace()
return response
#curses_addstr(self.window, self.menu.x, self.menu.y, "Show menu")
def refresh(self):
self.window.refresh()
curses.panel.update_panels()
#self.window.move(0,0)
curses.doupdate()
def hide(self):
try:
self.panel.hide()
except:
pass
self.refresh()
#curses_addstr(self.window, self.menu.x, self.menu.y, "Hide menu")
class DrawCharPickerHandler:
def __init__(self, caller, window):
self.caller = caller # drawCharPicker
self.window = window
def pickChar(self):
maxLines, maxCol = self.window.getmaxyx()
#pdb.set_trace()
self.window.addstr(maxLines - 3, 0, "Enter a character to use for drawing: ")
prompting = True
curses.flushinp()
while prompting:
#c = self.window.getch()
c = self.window.get_wch()
time.sleep(0.01)
if c in [curses.KEY_F1]:
self.caller.appState.drawChar = chr(self.caller.caller.caller.chMap['f1'])
prompting = False
elif c in [curses.KEY_F2]:
self.caller.appState.drawChar = chr(self.caller.caller.caller.chMap['f2'])
prompting = False
elif c in [curses.KEY_F3]:
self.caller.appState.drawChar = chr(self.caller.caller.caller.chMap['f3'])
prompting = False
elif c in [curses.KEY_F4]:
self.caller.appState.drawChar = chr(self.caller.caller.caller.chMap['f4'])
prompting = False
elif c in [curses.KEY_F5]:
self.caller.appState.drawChar = chr(self.caller.caller.caller.chMap['f5'])
prompting = False
elif c in [curses.KEY_F6]:
self.caller.appState.drawChar = chr(self.caller.caller.caller.chMap['f6'])
prompting = False
elif c in [curses.KEY_F7]:
self.caller.appState.drawChar = chr(self.caller.caller.caller.chMap['f7'])
prompting = False
elif c in [curses.KEY_F8]:
self.caller.appState.drawChar = chr(self.caller.caller.caller.chMap['f8'])
prompting = False
elif c in [curses.KEY_F9]:
self.caller.appState.drawChar = chr(self.caller.caller.caller.chMap['f9'])
prompting = False
elif c in [curses.KEY_F10]:
self.caller.appState.drawChar = chr(self.caller.caller.caller.chMap['f10'])
prompting = False
elif c in [27, 13, curses.KEY_ENTER]: # 27 = esc, 13 = enter, cancel
prompting = False
elif type(c) == str: # Is a printable/unicode character
if c.isprintable():
self.caller.appState.drawChar = c
prompting = False
else: # is an integer, but probably still a printable character
try:
if chr(c).isprintable():
newChar = chr(c)
self.caller.appState.drawChar = newChar
prompting = False
except:
pass
pass
#self.caller.caller.drawCharPickerButton.label = self.caller.appState.drawChar
self.caller.caller.drawCharPickerButton.set_label(self.caller.appState.drawChar)
self.window.addstr(maxLines - 3, 0, " ")
self.caller.caller.caller.refresh()
class ColorPickerHandler:
def __init__(self, colorPicker, window, width=38, height=8):
self.colorPicker = colorPicker
self.colorMode = colorPicker.colorMode # "256" or "16"
self.totalColors = colorPicker.totalColors
self.parentWindow = window
self.x = colorPicker.x
self.y = colorPicker.y
self.parentWindow = colorPicker.caller.stdscr
self.appState = colorPicker.caller.appState
self.ansi = colorPicker.caller.ansi
# figure out picker size
#total = curses.COLORS
#total = curses.COLORS
realmaxY,realmaxX = self.parentWindow.getmaxyx()
self.realmaxY = realmaxY
self.realmaxX = realmaxX
self.height = height
self.width = width
self.colorGrid = [[0 for i in range(self.width)] for j in range(self.height)]
self.window = curses.newwin(self.height, self.width, self.x, self.y)
self.window.keypad(True)
self.curses_win = self.window
self.panel = curses.panel.new_panel(self.curses_win)
self.panel.hide()
#self.fillChar = 9608 # unicode block
self.fillChar = self.appState.colorPickChar # unicode block
self.origin = self.x - 2
#self.move(0,self.x - 2)
self.move(0,self.origin)
def drawBorder(self):
""" Draw a highlighted border around color picker to show it is selected. """
x = self.x - 2
y = self.y - 1
width = self.width + 1
borderColor = curses.color_pair(self.appState.theme['menuBorderColor'])
curses_addstr(self.parentWindow, y, x, ("." * (width)), borderColor | curses.A_BOLD)
for line in range(1, self.height + 1):
curses_addstr(self.parentWindow, y + line, x, (":"), borderColor | curses.A_BOLD)
def hideBorder(self):
x = self.x - 2
y = self.y - 1
width = self.width + 1
borderColor = curses.color_pair(self.appState.theme['menuBorderColor'])
curses_addstr(self.parentWindow, y, x, (" " * (width)))
for line in range(1, self.height + 1):
curses_addstr(self.parentWindow, y + line, x, (" "))
def show(self):
#self.showFgPicker()
self.updateFgPicker()
#self.updateBgPicker()
#prompting = False
#print('\033[?1003l') # disable mouse movement tracking (xterm api)
#curses.mousemask(1)
#curses_cursorOff()
# populate window with colors
self.panel.top()
#self.move(0,self.x - 6)
self.panel.show()
#oldColor = self.colorPicker.caller.colorfg
#color = self.colorPicker.caller.colorfg
#if self.appState.colorPickerSelected:
# prompting = True
#else:
# prompting = False
#if self.appState.colorPickerSelected:
# if self.appState.sideBarShowing:
# self.drawBorder()
def hide(self):
self.panel.bottom()
try:
self.panel.hide()
except:
pass
def move(self, x, y):
self.x = x
self.y = y
try:
self.panel.move(y, x)
except Exception as E:
#self.colorPicker.caller.notify(f"Exception {E}")
#pdb.set_trace()
pass
self.origin = self.y
def updateFgPicker(self):
line = 0
col = 1
if self.colorMode == "16":
col = 0
if self.colorMode == "256":
plain_color_pair = curses.color_pair(9)
elif self.colorMode == "16":
plain_color_pair = curses.color_pair(8)
#maxWidth = self.realmaxX
#maxHeight = self.realmaxY
#for fg in range(0,curses.COLORS): # 0-255
width_counter = 0 # for color block width
#for fg in range(1,self.appState.totalFgColors+1): # 0-255
firstColor = 1
if self.colorPicker.appState.iceColors:
firstColor = 0
for fg in range(firstColor,self.totalColors+1): # 0-255
#color_pair = curses.color_pair(fg)
if self.colorMode == "256":
color_pair = curses.color_pair(fg)
elif self.colorMode == "16":
try:
color_pair_number = self.ansi.colorPairMap[(fg, 0)]
color_pair = curses.color_pair(color_pair_number)
except KeyError:
pdb.set_trace()
if col >= self.width - 2:
col = 0
line += 1
if self.colorMode == "256":
if fg == 16: # first color for fancy displayed block color palette thing
line += 1
col = 0
if fg > 16:
width_counter += 1
if width_counter == 36:
width_counter = 0
#line += 1
try:
self.colorGrid[line][col] = fg
except IndexError:
pass
#if line > 0:
# pdb.set_trace()
#curses_addstr(self.window, self.colorPicker.y + line, self.colorPicker.x + col, chr(self.fillChar), color_pair)
# if fg == app's current fg, draw it as a * instead of self.fillChar
bg = self.colorPicker.caller.colorbg
if self.colorMode == "16":
bg += 1
if bg == 8:
bg = 0
if fg == self.colorPicker.caller.colorfg or fg == 0 and bg == 8:
if fg == 1: # black
if self.appState.colorPickerSelected:
if fg == bg:
curses_addstr(self.window, line, col, 'X', plain_color_pair | curses.A_UNDERLINE | curses.A_BOLD)
else:
curses_addstr(self.window, line, col, 'F', plain_color_pair | curses.A_UNDERLINE | curses.A_BOLD)
else:
if fg == bg:
curses_addstr(self.window, line, col, 'X', plain_color_pair)
else:
curses_addstr(self.window, line, col, 'F', plain_color_pair)
else:
if self.appState.colorPickerSelected:
if self.colorMode == "256":
if fg == bg:
curses_addstr(self.window, line, col, 'X', color_pair | curses.A_UNDERLINE | curses.A_BOLD)
else:
curses_addstr(self.window, line, col, 'F', color_pair | curses.A_UNDERLINE | curses.A_BOLD)
if self.colorMode == "16":
if fg > 8 and self.appState.iceColors == False:
curses_addstr(self.window, line, col, 'F', color_pair | curses.A_UNDERLINE | curses.A_BOLD)
else:
curses_addstr(self.window, line, col, 'F', color_pair | curses.A_UNDERLINE )
else:
if self.colorMode == "256":
if fg == bg:
curses_addstr(self.window, line, col, 'X', color_pair | curses.A_BOLD)
else:
curses_addstr(self.window, line, col, 'F', color_pair | curses.A_BOLD)
if self.colorMode == "16":
if fg == bg:
if fg > 8 and self.appState.iceColors == False:
curses_addstr(self.window, line, col, 'X', color_pair | curses.A_BOLD)
else:
curses_addstr(self.window, line, col, 'X', color_pair)
else:
if fg > 8 and self.appState.iceColors == False:
curses_addstr(self.window, line, col, 'F', color_pair | curses.A_BOLD)
else:
curses_addstr(self.window, line, col, 'F', color_pair)
# 16 color, black background (8), showing color 1 (black). . why the fuck is it 9 lol
elif self.colorMode == "16" and fg == 1 and bg == 9:
if self.appState.colorPickerSelected:
curses_addstr(self.window, line, col, 'B', plain_color_pair | curses.A_UNDERLINE)
else:
curses_addstr(self.window, line, col, 'B', plain_color_pair)
# 16 color, black background (8), showing color 8 (grey)
elif self.colorMode == "16" and fg == 9 and bg == 9:
# draw fill character unmodified
if fg > 8 and self.appState.iceColors == False:
curses_addstr(self.window, line, col, self.fillChar, color_pair | curses.A_BOLD)
else:
curses_addstr(self.window, line, col, self.fillChar, color_pair)
elif fg == bg:
if self.appState.colorPickerSelected:
if self.colorMode == "256":
curses_addstr(self.window, line, col, 'B', color_pair | curses.A_UNDERLINE)
elif self.colorMode == "16" and fg == 9: # black, so show as default color
curses_addstr(self.window, line, col, 'B', plain_color_pair | curses.A_UNDERLINE)
elif self.colorMode == "16" and fg == 0: # black, so show as default color
curses_addstr(self.window, line, col, 'B', plain_color_pair | curses.A_UNDERLINE)
elif self.colorMode == "16" and bg != 0:
curses_addstr(self.window, line, col, 'B', color_pair | curses.A_UNDERLINE)
else:
if self.colorMode == "256":
curses_addstr(self.window, line, col, 'B', color_pair | curses.A_BOLD)
elif self.colorMode == "16" and fg == 1: # black, so show as default color
curses_addstr(self.window, line, col, 'B', plain_color_pair)
elif self.colorMode == "16" and fg == 9: # black, so show as default color
curses_addstr(self.window, line, col, 'B', plain_color_pair)
elif self.colorMode == "16" and bg != 0:
curses_addstr(self.window, line, col, 'B', color_pair | curses.A_UNDERLINE)
#if self.colorMode == "16" and fg == 8: # black, so show as default color
# curses_addstr(self.window, line, col, 'B', plain_color_pair)
#elif self.colorMode == "16":
# curses_addstr(self.window, line, col, 'B', color_pair)
else:
if self.colorMode == "256":
curses_addstr(self.window, line, col, self.fillChar, color_pair)
elif self.colorMode == "16":
# draw bright colors in 16 color mode
if fg > 8 and self.appState.iceColors == False:
curses_addstr(self.window, line, col, self.fillChar, color_pair | curses.A_BOLD)
#debug_string = str(color_pair)
#self.colorPicker.caller.notify(debug_string)
else:
curses_addstr(self.window, line, col, self.fillChar, color_pair)
if self.colorMode == "16" and bg == 0 and fg == 0: # black bg in 16 color mode
if self.appState.colorPickerSelected:
curses_addstr(self.window, line, col, 'B', plain_color_pair | curses.A_UNDERLINE)
else:
curses_addstr(self.window, line, col, 'B', plain_color_pair)
col += 1
curses_addstr(self.window, line, col + 5, " ", color_pair)
curses_addstr(self.window, line, col + 5, str(self.colorPicker.caller.colorfg), color_pair)
def showFgPicker(self, message=None):
#self.colorPicker.caller.notify(f"showFgPicker")
self.showColorPicker(type="fg", message=message)
def move_up_256(self):
if self.colorMode == "256":
color = self.colorPicker.caller.colorfg
if color < 32:
color -= 16
elif color > 31 and color < 52:
color = 15
else:
color -= self.width - 2
if color < 1:
color = 1
self.colorPicker.caller.setFgColor(color)
def move_down_256(self):
if self.colorMode == "256":
color = self.colorPicker.caller.colorfg
if color < 16:
color += 16
else:
color += self.width - 2
if color >= self.totalColors:
color = self.totalColors
self.colorPicker.caller.setFgColor(color)
def showColorPicker(self, type="fg", message=None):
#self.colorPicker.caller.notify(f"showColorPicker")
""" Shows picker, has UI loop for letting user pick color with keyboard or mouse """
if type == "fg":
self.updateFgPicker()
elif type == "bg":
self.updateBgPicker()
prompting = False
#print('\033[?1003l') # disable mouse movement tracking (xterm api)
#curses.mousemask(1)
curses_cursorOff()
# populate window with colors
self.panel.top()
#self.move(0,self.x - 6)
self.panel.show()
oldColor = self.colorPicker.caller.colorfg
color = self.colorPicker.caller.colorfg
if self.appState.colorPickerSelected:
prompting = True
#self.window.nodelay(0) # wait for input when calling getch
else:
prompting = False
if self.appState.colorPickerSelected:
if self.appState.sideBarShowing:
self.drawBorder()
#self.window.nodelay(1)
#self.colorPicker.caller.notify(f"showColorPicker() hit. {prompting=}")
mov = self.colorPicker.caller.mov
appState = self.colorPicker.caller.appState
# ^ Used to determine if we clicked in the canvas:
while(prompting):
time.sleep(0.01)
#self.colorPicker.caller.drawStatusBar()
self.update()
c = self.window.getch()
if c in [98, curses.KEY_LEFT, ord('h')]:
if color == 0:
color = self.totalColors
else:
color -= 1
#self.colorPicker.caller.colorfg = color
self.colorPicker.caller.setFgColor(color)
self.updateFgPicker()
self.colorPicker.caller.drawStatusBar()
elif c in [102, curses.KEY_RIGHT, ord('l')]:
color += 1
#if color >= curses.COLORS:
if color > self.totalColors:
color = 0
#self.colorPicker.caller.colorfg = color
self.colorPicker.caller.setFgColor(color)
self.updateFgPicker()
self.colorPicker.caller.drawStatusBar()
elif c in [curses.KEY_UP, ord('k')]:
if self.colorMode == "256":
if color < 32:
color -= 16
elif color > 31 and color < 52:
color = 15
else:
color -= self.width - 2
elif self.colorMode == "16":
self.colorPicker.caller.nextBgColor()
#color -= self.width - 2
if color <= 0:
color = 1
self.colorPicker.caller.setFgColor(color)
self.updateFgPicker()
self.colorPicker.caller.drawStatusBar()
elif c in [curses.KEY_DOWN, ord('j')]:
if self.colorMode == "256":
if color < 16:
color += 16
else:
color += self.width - 2
if color >= self.totalColors:
color = self.totalColors
self.colorPicker.caller.setFgColor(color)
elif self.colorMode == "16":
self.colorPicker.caller.prevBgColor()
#color += self.width - 2
self.updateFgPicker()
self.colorPicker.caller.drawStatusBar()
elif c == curses.KEY_HOME:
color = 1
self.colorPicker.caller.setFgColor(color)
self.updateFgPicker()
elf.colorPicker.caller.drawStatusBar()
elif c == curses.KEY_END:
#color = 255
color = self.totalColors
self.colorPicker.caller.setFgColor(color)
self.updateFgPicker()
self.colorPicker.caller.drawStatusBar()
elif c in [13, curses.KEY_ENTER, 9, 353, 27]: # Return, Accept color. 9=tab, 353=shift-tab, 27 = esc
#if not self.appState.stickyColorPicker:
if not self.appState.sideBarShowing:
self.hide()
prompting = False
if c == 27: # esc, cancel
self.colorPicker.caller.colorfg = oldColor
self.appState.colorPickerSelected = False
c = None
self.updateFgPicker()
self.hideBorder()
#self.colorPicker.caller.notify(f"{c=}, {prompting=}")
if not self.colorPicker.caller.playing: # caller.caller is the main UI thing
self.window.nodelay(0) # wait
#return color
elif c == curses.KEY_MOUSE:
try:
_, mouseX, mouseY, _, mouseState = curses.getmouse()
except:
pass
if not self.appState.hasMouseScroll:
curses.BUTTON5_PRESSED = 0
curses.BUTTON4_PRESSED = 0
if mouseState & curses.BUTTON4_PRESSED: # wheel down?
color += 1
if color > appState.totalFgColors:
if appState.colorMode == "16":
color = 1
elif appState.colorMode == "256":
color = 0
self.colorPicker.caller.setFgColor(color)
self.updateFgPicker()
self.colorPicker.caller.drawStatusBar()
elif mouseState & curses.BUTTON5_PRESSED: # wheel up?
color -= 1
if appState.colorMode == "16":
if color <= 0:
color = appState.totalFgColors
elif appState.colorMode == "256":
if color < 0:
color = appState.totalFgColors
self.colorPicker.caller.setFgColor(color)
self.updateFgPicker()
self.colorPicker.caller.drawStatusBar()
elif mouseY >= self.origin and mouseX > self.x and mouseX < self.x + len(self.colorGrid[0])-2: # cpicked in the color picker
self.hideBorder()
#self.colorPicker.caller.notify(f"DEBUG: self.origin={self.origin}, self.x = {self.x}. mouseX={mouseX}, mouseY={mouseY}", pause=True)
self.gotClick(mouseX, mouseY)
prompting = False
elif mouseY < mov.sizeY and mouseX < mov.sizeX \
and mouseY + appState.topLine < appState.topLine + self.colorPicker.caller.statusBarLineNum:
# we did click in the canvas.
self.hideBorder()
prompting = False
if not prompting:
if not self.appState.sideBarShowing:
self.hide()
#self.hide()
#prompting = False
elif c == 27: # normal esc, Cancel
c = self.window.getch()
if c == curses.ERR: # Just esc was hit, no other escape sequence
self.hideBorder()
self.colorPicker.caller.setFgColor(oldColor)
self.updateFgPicker()
if not self.appState.sideBarShowing:
self.hide()
#self.hide()
prompting = False
# Show message, eg: "pick new color"
#curses_addstr(self.window, self.appState.realmaxX - 2, 0, message, curses.color_pair(self.appState.theme['notificationColor']))
if not self.appState.colorPickerSelected:
if self.appState.sideBarShowing:
self.hideBorder()
self.appState.colorPickerSelected = False # done prompting
self.updateFgPicker()
if not self.appState.sideBarShowing:
self.hide()
#self.hide()
curses_cursorOn()
self.window.nodelay(0)
if self.colorPicker.caller.playing:
self.window.nodelay(1)
#curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
#print('\033[?1003h') # enable mouse tracking
return color
def gotClick(self, mouseX, mouseY):
# If clicked on a color, set an FG color
if not self.colorPicker.hidden and mouseY >= self.origin and mouseX < + self.x + len(self.colorGrid[0])-2: # cpicked in the color picker
clickedCol = mouseX - self.x
clickedLine = mouseY - self.origin
if mouseY < self.origin + self.height:
if self.appState.colorMode == "16":
if self.colorGrid[clickedLine][clickedCol] != 0:
color = self.colorGrid[clickedLine][clickedCol]
self.colorPicker.caller.setFgColor(color)
self.updateFgPicker()
elif self.appState.colorMode == "256":
if self.colorGrid[clickedLine][clickedCol] != 0:
color = self.colorGrid[clickedLine][clickedCol]
self.colorPicker.caller.setFgColor(color)
self.updateFgPicker()
if self.colorGrid[clickedLine][clickedCol] == 0:
if clickedLine == 0 and clickedCol == 0: # true black, not a fake black
color = self.colorGrid[clickedLine][clickedCol]
self.colorPicker.caller.setFgColor(color)
self.updateFgPicker()
def gotDoubleClick(self, mouseX, mouseY):
# set a BG color
if not self.colorPicker.hidden and mouseY >= self.origin and mouseX < + self.x + len(self.colorGrid[0])-2: # cpicked in the color picker
clickedCol = mouseX - self.x - 1
clickedLine = mouseY - self.origin
if mouseY < self.origin + self.height:
if self.colorGrid[clickedLine][clickedCol] != 0:
color = self.colorGrid[clickedLine][clickedCol]
if self.colorPicker.caller.appState.colorMode == "16" and color < 9:
# Only set BG color if it's one of the non-bright colors. (Sorry, no ice yet)
self.colorPicker.caller.setBgColor(color)
self.updateFgPicker()
def update(self):
curses.panel.update_panels()
curses.doupdate()
self.window.refresh()
class ColorSwatchHandler:
def __init__(self, colorSwatch, window):
self.colorSwatch = colorSwatch
self.window = window
self.fillChar = 9608 # unicode block
def draw(self, plusX=0, plusY=0):
for c in range(0, len(self.colorSwatch.bank)-1):
curses_addstr(self.window, self.colorSwatch.y, self.colorSwatch.x, chr(self.fillChar))
class FgBgColorPickerHandler:
def __init__(self, fgBgPicker, x=0, y=0):
self.fgBgPicker = fgBgPicker
self.window = fgBgPicker.window
self.x = x
self.y = y
#self.on_click = on_click
def draw(self, plusX=0, plusY=0):
curses_addstr(self.window, self.y, self.x, "F: G: ")
class ToolTipHandler:
""" Draw and hide tooltips """
#def __init__(self, tooltip, window, appState=None):
def __init__(self, tooltip, context):
self.tooltip = tooltip
self.window = context.window
self.appState = context.appState
def draw(self):
tipString = self.tooltip.hotkey
tipColor = self.appState.theme['clickHighlightColor'] | curses.A_BOLD | curses.A_UNDERLINE
#curses_addstr(self.window, self.tooltip.column, self.tooltip.row, tipString, tipColor)
curses_addstr(self.window, self.tooltip.row, self.tooltip.column, tipString, tipColor)
def show(self):
self.draw()
def hide (self):
pass
class ButtonHandler:
""" hook into Curses to draw button
"""
def __init__(self, button, window, on_click, appState=None):
#self.label = button.label
self.button = button
self.window = window
self.x = self.button.x
self.y = self.button.y
self.on_click = on_click
self.appState = appState
self.color = curses.color_pair(self.appState.theme['clickColor']) | curses.A_BOLD # bright green, clickable
def draw(self, plusX=0, plusY=9):
if not self.button.invisible:
self.color = curses.color_pair(self.appState.theme['clickColor']) | curses.A_BOLD # reload color from theme
textColor = self.color
if self.button.selected:
#curses_notify(self.window, "Tacos")
textColor = textColor | curses.A_REVERSE
if self.button: # a selector type button, to be iframed in []s
buttonString = f"[{self.button.label}]"
else:
buttonString = self.button.label
#curses_addstr(self.window, plusX + self.button.x, plusY + self.button.y, self.button.label)
#curses_addstr(self.window, plusX + self.button.x, plusY + self.button.y, self.button.label, self.color)
#curses_addstr(self.window, self.button.realX, self.button.realY, self.button.label, textColor)
curses_addstr(self.window, self.button.realX, self.button.realY, buttonString, textColor)
# render the button on the window
if self.button.persistant_tooltip or not self.button.tooltip_hidden:
if self.button.get_tooltip_command():
toolTip :str = self.button.get_tooltip_command()
tipColor = self.appState.theme['clickHighlightColor'] | curses.A_BOLD | curses.A_UNDERLINE
# Figure out if the hint is in the button label, if so, place it over it with
# an index offset
tipColOffset = 0
if toolTip.lower() in self.button.label.lower():
tipColOffset = self.button.label.lower().index(toolTip.lower()) + 1
#curses_notify(self.window, "Gorditas")
# keep the tip from going off screen for some buttons
if self.button.realY == 0 and tipColOffset == 0:
tipColOffset += 1
# Print it next to the button for now
curses_addstr(self.window, self.button.realX, self.button.realY + tipColOffset, toolTip, tipColor)
def showToolTip(self):
self.button.tooltip_hidden = False
def hideToolTip(self):
self.button.tooltip_hidden = True
# Cover up with spaces
#if not self.button.hidden:
# self.button.draw()
#if self.button.get_tooltip_command() and not self.button.hidden:
# toolTip :str = self.button.get_tooltip_command()
# toolTip = " " * len(toolTip)
# curses_addstr(self.window, self.button.realX, self.button.realY, toolTip)
def hide(self):
self.hidden = True
def on_click(self):
curses_addstr(self.window, 0, 0, f"{self.button.label} clicked")
def update_real_xy(x=0, y=0):
self.button.realX = x
self.button.realY = y
def handle_event(self, event):
# handle events for the button
if event.type == 'click':
self.on_click()
return True
class StatusBarHandler:
def __init__(self, statusBar, window):
self.statusBar = statusBar
self.window = window
self.panel = curses.panel.new_panel(self.window)
#curses_addstr(window, self.statusBar.x, self.statusBar.y, "Fnord")
#self.on_click = on_click
def draw(self, plusX=0, plusY=0):
pass
#for item in self.statusBar.items:
# curses_addstr(self.window, self.statusBar.x + item.x, self.statusBar.y + item.y, item.label)
def handle_event(self, event):
# handle events for the button
if event.type == 'click':
self.on_click()
return True
durdraw-0.28.0/durdraw/durdraw_undo.py 0000664 0000000 0000000 00000006215 14704761225 0020004 0 ustar 00root root 0000000 0000000 from copy import copy, deepcopy
class UndoManager(): # pass it a UserInterface object so Undo can tell UI
# when to switch to another saved movie state.
""" Manages undo/redo "stack" by storing the last 100 movie states
in a list. Takes a UserInterface object for syntax. methods for
push, undo and redo """
def __init__(self, ui, appState = None):
self.ui = ui
self.undoIndex = 0 # will be 0 when populated with 1 state.
self.modifications = 0 #
self.undoList = []
self.historySize = 100 # default, but really determined by
self.appState = appState
# AppState values passed to setHistorySize() below.
self.push() # push initial state
def push(self): # maybe should be called pushState or saveState?
""" Take current movie, add to the end of a list of movie
objects - ie, push current state onto the undo stack. """
if self.modifications > 0:
if self.appState.modified == False:
self.appState.modified = True
self.modifications += 1
if len(self.undoList) >= self.historySize: # How far back our undo history can
# go. Make this configurable.
# if undo stack == full, dequeue from the bottom
self.undoList.pop(0)
# if undoIndex isn't indexing the last item in undoList,
# ie we have redo states, remove all items after undoList[undoIndex]
self.undoList = self.undoList[0:self.undoIndex] # trim list
# then add the new state to the end of the queue.
self.undoList.append(deepcopy(self.ui.mov))
# last item added == at the end of the list, so..
self.undoIndex = len(self.undoList) # point index to last item
def undo(self):
if self.modifications > 1:
self.modifications = self.modifications - 1
if self.modifications == 2:
self.appState.modified = False
if self.undoIndex == 1: # nothing to undo
self.ui.notify("Nothing to undo.")
return False
# if we're at the end of the list, push current state so we can
# get back to it. A bit confusing.
if self.undoIndex == len(self.undoList):
self.push()
self.undoIndex -= 1
self.undoIndex -= 1
self.ui.mov = self.undoList[self.undoIndex] # set UI movie state
return True # succeeded
def redo(self):
if self.undoIndex < (len(self.undoList) -1): # we can redo
self.undoIndex += 1 # go to next redo state
self.modifications += 1
self.ui.mov = self.undoList[self.undoIndex]
if self.appState.modified == False:
self.appState.modified = True
else:
self.ui.notify("Nothing to redo.")
def setHistorySize(self, historySize):
""" Defines the max number of undo states we will save """
self.historySize = historySize
durdraw-0.28.0/durdraw/durf/ 0000775 0000000 0000000 00000000000 14704761225 0015671 5 ustar 00root root 0000000 0000000 durdraw-0.28.0/durdraw/durf/bsd.durf 0000664 0000000 0000000 00000027220 14704761225 0017326 0 ustar 00root root 0000000 0000000 jfbsd.durf oW 2I#ڛYwCSi3LRd2w;H;rbYI/c,'ER\>˯'wՇuV/=woգO']Wzoo,?xz'_?ꏼc?]=R~(_U͛χzϷ/>~ǿ^@O~ߪN>{oUub'O37^n1,]寯>}u
7,{4{y=ח+ɋ'W/Ts\\<ŪƫS^-o}uSV\]^VC}WxU}Wjcxq{rC5W9{˫>NI.roW*[m*EWU5FU 8AvY|^zmWW_[e1!;5\_fʪ=QW/~UY}x=/v///뇮>ǯj7/>۳Alu{pT
_uy^fol=n;sޯ}\lTO[z}rM7PIqQj}yZzj翼Ư@;8W&_56L=Z5|vYJn│z+{}cŋ/>Zב^^.oEY
a=ݫ_{Wbtw_}?]yK.]t5ry̲ÿWy|Oɋ&o8JyS+cSN:u[ܯK.~_W?N}ui,ƝGmZۯ恭yÖ[W2diqYܕZ;'y)۴\.ʘZɇPf괚kˆUgݲ3W