Metadata property added to call ref. Checkin of the latest readme_creator files
This commit is contained in:
parent
faa701e3de
commit
fcb3cc7bde
|
@ -160,6 +160,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -289,7 +299,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -491,6 +500,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -605,7 +624,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -756,6 +774,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -851,7 +879,6 @@ unhide_row()
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -1010,6 +1037,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -1120,7 +1157,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -1325,6 +1361,16 @@ Parameter Descriptions:
|
|||
| List[List[Element]] | rows | The rows of Elements |
|
||||
| (Column) | **RETURN** | Used for chaining
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -1438,7 +1484,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -1633,6 +1678,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -1747,7 +1802,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -1959,6 +2013,16 @@ Parameter Descriptions:
|
|||
| List[List[Element]] | rows | The rows of Elements |
|
||||
| (Frame) | **RETURN** | Used for chaining
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -2059,7 +2123,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -2615,6 +2678,16 @@ Parameter Descriptions:
|
|||
|--|--|--|
|
||||
| int | figure | value returned by tkinter when creating the figure / drawing |
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -2725,7 +2798,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -3184,6 +3256,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -3269,7 +3351,6 @@ unhide_row()
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -3400,6 +3481,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -3539,7 +3630,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -3742,6 +3832,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -3867,7 +3967,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -4082,6 +4181,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -4219,7 +4328,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -4421,6 +4529,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -4521,7 +4639,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -4767,6 +4884,16 @@ Restore a previously re-reouted stdout back to the original destination
|
|||
restore_stdout()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -4900,7 +5027,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -5080,6 +5206,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -5199,7 +5335,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -5349,6 +5484,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -5473,7 +5618,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -5641,6 +5785,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -5740,7 +5894,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -5887,6 +6040,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -6008,7 +6171,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -6204,6 +6366,16 @@ Sets all Radio Buttons in the group to not selected
|
|||
reset_group()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -6313,7 +6485,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -6504,6 +6675,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -6609,7 +6790,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -6782,6 +6962,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -6892,7 +7082,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -7070,6 +7259,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -7177,7 +7376,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -7373,6 +7571,16 @@ Create a tkinter event that mimics user clicking on a tab. Must have called wind
|
|||
select()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -7476,7 +7684,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -7701,6 +7908,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -7800,7 +8017,6 @@ unhide_row()
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -8013,6 +8229,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -8122,7 +8348,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -8313,6 +8538,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -8420,7 +8655,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -8681,6 +8915,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -8804,7 +9048,6 @@ Parameter Descriptions:
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -9016,6 +9259,16 @@ Hide the entire row an Element is located on.
|
|||
hide_row()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an Element property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### set_cursor
|
||||
|
||||
Sets the cursor for the current Element.
|
||||
|
@ -9101,7 +9354,6 @@ unhide_row()
|
|||
#### property: visible
|
||||
|
||||
Returns visibility state for the element. This is a READONLY property
|
||||
To control visibility, use the element's update method
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|
@ -9815,6 +10067,16 @@ Maximize the window. This is done differently on a windows system versus a linux
|
|||
maximize()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is available for all windows. You can set to any value.
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### minimize
|
||||
|
||||
Minimize this window to the task bar
|
||||
|
@ -10627,6 +10889,16 @@ Hides the icon
|
|||
hide()
|
||||
```
|
||||
|
||||
### metadata
|
||||
|
||||
#### property: metadata
|
||||
|
||||
Metadata is an SystemTray property that you can use at any time to hold any value
|
||||
|
||||
|Type|Name|Meaning|
|
||||
|---|---|---|
|
||||
|(Any)| **return** | the current metadata value |
|
||||
|
||||
### notify
|
||||
|
||||
Displays a "notification window", usually in the bottom right corner of your display. Has an icon, a title, and a message
|
||||
|
|
|
@ -0,0 +1,692 @@
|
|||
import time
|
||||
import subprocess,re,datetime,time,os,platform,json,PySimpleGUI as sg; from subprocess import Popen; from make_real_readme import main
|
||||
|
||||
# mkdir
|
||||
import os
|
||||
cd = CD = os.path.dirname(os.path.abspath(__file__))
|
||||
dir_name = os.path.join(cd, 'output')
|
||||
if not os.path.exists(dir_name): os.mkdir(dir_name)
|
||||
else: print(f'Такая папка уже есть: "{dir_name}"')
|
||||
|
||||
|
||||
|
||||
sg.theme('Dark2')
|
||||
cd = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
def readfile(filename):
|
||||
with open(filename, 'r', encoding='utf-8') as ff: return ff.read()
|
||||
def writefile(fpath, content):
|
||||
with open(fpath, 'w', encoding='utf-8') as ff: ff.write(content)
|
||||
def writejson(a_path:str, a_dict:dict) -> None:
|
||||
with open(a_path, 'w', encoding='utf-8') as output_file: json.dump(a_dict, output_file, ensure_ascii=False, indent=2)
|
||||
def readjson(a_path:str) -> dict:
|
||||
with open(a_path, 'r', encoding='utf-8') as f: return json.load(f)
|
||||
|
||||
|
||||
def openfile(a_path):
|
||||
# File exists?
|
||||
if not os.path.exists(a_path): return sg.Popup(f"Error! This file doesn't exists: {a_path}")
|
||||
|
||||
# check: OS
|
||||
if 'Windows' in platform.system():
|
||||
os.startfile(a_path)
|
||||
|
||||
elif 'Linux' in platform.system():
|
||||
Popen(f'exo-open "{a_path}"', shell=True)
|
||||
|
||||
def opendir(a_path):
|
||||
# Folder exists?
|
||||
if not os.path.exists(a_path): return sg.Popup(f"Error! This directory doesn't exists: {a_path}")
|
||||
|
||||
try:
|
||||
# check: OS
|
||||
if 'Windows' in platform.system():
|
||||
os.startfile(a_path)
|
||||
elif 'Linux' in platform.system():
|
||||
Popen(f'exo-open --launch FileManager --working-directory "{a_path}"', shell=True)
|
||||
except Exception as e:
|
||||
sg.Popen(f"Error, can't open a file: '{e}'")
|
||||
|
||||
|
||||
########################################################################
|
||||
# __ _ _ #
|
||||
# / _(_) | | #
|
||||
# __ __ ___ ___ _ __ | |_ _ __ _ | |__ ___ _ __ ___ #
|
||||
# \ \ / / / __/ _ \| '_ \| _| |/ _` | | '_ \ / _ \ '__/ _ \ #
|
||||
# \ V / | (_| (_) | | | | | | | (_| | | | | | __/ | | __/ #
|
||||
# \_/ \___\___/|_| |_|_| |_|\__, | |_| |_|\___|_| \___| #
|
||||
# __/ | #
|
||||
# |___/ #
|
||||
########################################################################
|
||||
def load_configs(): return readjson(os.path.join(cd, 'app_configs.json'))
|
||||
def save_configs(a_config:dict): writejson(os.path.join(cd, 'app_configs.json'), a_config)
|
||||
|
||||
|
||||
|
||||
APP_CONFIGS = load_configs()
|
||||
README_OFILENAME = APP_CONFIGS['README_OFILE']
|
||||
CALL_REFERENCE_OFILENAME = APP_CONFIGS['CALL_REF_OFILE']
|
||||
|
||||
##-#-#-# ##-#-#-#
|
||||
# Post-process logic
|
||||
##-#-#-# ##-#-#-#
|
||||
insert_md_section_for__class_methods = False
|
||||
remove_repeated_sections_classmethods = False
|
||||
|
||||
import time
|
||||
def timeit(f):
|
||||
def wrapper(*args, **kwargs):
|
||||
start = time.time()
|
||||
res = f(*args, **kwargs)
|
||||
end = time.time()
|
||||
# print('\nНачало в : ', start)
|
||||
# print('\n ({}) Начало в : '.format(f.__name__, start))
|
||||
# print('Окончено в : ', end)
|
||||
# print('Длительность: ', end - start)
|
||||
# print('')
|
||||
return res
|
||||
return wrapper
|
||||
|
||||
class BESTLOG(object):
|
||||
def __init__(self, filename):
|
||||
# my_file = logging.FileHandler(filename, mode='w')
|
||||
# my_file.setLevel(logging.DEBUG)
|
||||
# my_file.setFormatter(logging.Formatter('%(asctime)s>%(levelname)s: %(message)s'))
|
||||
# logger = logging.getLogger(__name__)
|
||||
# logger.setLevel(logging.DEBUG)
|
||||
# logger.addHandler(my_file)
|
||||
self.filename = filename
|
||||
self.json_name = filename + '.json'
|
||||
self.error_list = []
|
||||
self.warning_list = []
|
||||
self.info_list = []
|
||||
self.debug_list = []
|
||||
self.tick_amount=1
|
||||
self.names = self.messages_names = 'error warning info debug'.split(' ')
|
||||
|
||||
def tick(self):
|
||||
self.tick_amount+=1
|
||||
return self.tick_amount
|
||||
|
||||
#######################################################################
|
||||
# __ _ _ _ #
|
||||
# / _| | | (_) | #
|
||||
# | |_ ___ _ __ | |_ _ __ __ _ _ __ ___ _ __ _| | ___ _ __ #
|
||||
# | _/ _ \| '__| | __| '__/ _` | '_ \/ __| '_ \| | |/ _ \ '__| #
|
||||
# | || (_) | | | |_| | | (_| | | | \__ \ |_) | | | __/ | #
|
||||
# |_| \___/|_| \__|_| \__,_|_| |_|___/ .__/|_|_|\___|_| #
|
||||
# | | #
|
||||
# |_| #
|
||||
#######################################################################
|
||||
def error(self, m, metadata={}):
|
||||
self.error_list.append([self.tick(), m, metadata])
|
||||
def warning(self, m, metadata={}):
|
||||
self.warning_list.append([self.tick(), m, metadata])
|
||||
def info(self, m, metadata={}):
|
||||
self.info_list.append([self.tick(), m, metadata])
|
||||
def debug(self, m, metadata={}):
|
||||
self.debug_list.append([self.tick(), m, metadata])
|
||||
|
||||
##########################################
|
||||
# __ #
|
||||
# / _| #
|
||||
# | |_ ___ _ __ _ __ ___ ___ #
|
||||
# | _/ _ \| '__| | '_ ` _ \ / _ \ #
|
||||
# | || (_) | | | | | | | | __/ #
|
||||
# |_| \___/|_| |_| |_| |_|\___| #
|
||||
# #
|
||||
# #
|
||||
##########################################
|
||||
def tolist(self): return zip([self.error_list, self.warning_list, self.info_list, self.debug_list], self.names)
|
||||
def todict(self): return {'error' : self.error_list, 'warning' : self.warning_list, 'info' : self.info_list, 'debug' : self.debug_list}
|
||||
@timeit
|
||||
def save(self):
|
||||
'''
|
||||
{
|
||||
'message_type' : message_type,
|
||||
'message_text' : m_text,
|
||||
'message_time' : m_time,
|
||||
'message_metadata' : m_metadata
|
||||
}
|
||||
'''
|
||||
all_messages_list = []
|
||||
for messages, message_type in self.tolist():
|
||||
results_ = [{'message_type' : message_type,
|
||||
'message_text' : m_text,
|
||||
'message_time' : m_time,
|
||||
'message_metadata' : m_metadata}
|
||||
for m_time, m_text, m_metadata in messages]
|
||||
all_messages_list.extend(results_)
|
||||
|
||||
# sort messages on time
|
||||
all_messages_list = sorted(all_messages_list,
|
||||
key=lambda x: x['message_time'])
|
||||
|
||||
# convert time
|
||||
# for i in all_messages_list: i['message_time'] = i['message_time'].strftime('%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
writejson(self.json_name, all_messages_list)
|
||||
@timeit
|
||||
def load(self, **kw):
|
||||
'''
|
||||
return dict with messages
|
||||
|
||||
kw = {
|
||||
use_psg_color : bool
|
||||
show_time : bool
|
||||
}
|
||||
'''
|
||||
|
||||
# plan:
|
||||
# read json, convert time
|
||||
|
||||
# read
|
||||
all_messages_list = readjson(self.json_name)
|
||||
# convert time
|
||||
# for i in all_messages_list: i['message_time'] = datetime.datetime.strptime(i['message_time'], '%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
def format_message(message):
|
||||
if kw['show_time']:
|
||||
return str(message['message_time']) + ':' + message['message_text']
|
||||
else:
|
||||
return message['message_text']
|
||||
|
||||
|
||||
#=========#
|
||||
# 4 lists #
|
||||
#=========#
|
||||
error_list = [i for i in all_messages_list if i['message_type'] == 'error']
|
||||
warning_list = [i for i in all_messages_list if i['message_type'] == 'warning']
|
||||
info_list = [i for i in all_messages_list if i['message_type'] == 'info']
|
||||
debug_list = [i for i in all_messages_list if i['message_type'] == 'debug']
|
||||
|
||||
|
||||
#=================#
|
||||
# and 1 more list #
|
||||
#=================#
|
||||
# colors = {'warning' : 'magenta', 'info' : 'black'}
|
||||
colors = {'warning' : 'blue', 'info' : 'black'}
|
||||
warning_info_ = []
|
||||
for message in sorted(warning_list + info_list, key=lambda x: x['message_time']):
|
||||
if kw['use_psg_color']:
|
||||
warning_info_.append([ format_message(message),
|
||||
colors.get(message['message_type']) ])
|
||||
else:
|
||||
warning_info_.append(format_message(message))
|
||||
|
||||
error_list = [format_message(i) for i in error_list]
|
||||
warning_list = [format_message(i) for i in warning_list]
|
||||
info_list = [format_message(i) for i in info_list]
|
||||
debug_list = [format_message(i) for i in debug_list]
|
||||
|
||||
return error_list, warning_list, info_list, debug_list, warning_info_
|
||||
@timeit
|
||||
def load_to_listbox(self):
|
||||
'''
|
||||
read .json
|
||||
'''
|
||||
return sorted(readjson(self.json_name),
|
||||
key=lambda x: x['message_time'])
|
||||
|
||||
@timeit
|
||||
def compile_call_ref(output_filename='LoG_call_ref', **kw):
|
||||
''' Compile a "5_call_reference.md" file'''
|
||||
|
||||
log_obj = BESTLOG(os.path.join(cd, output_filename))
|
||||
|
||||
main(logger=log_obj,
|
||||
main_md_file='markdown input files/5_call_reference.md',
|
||||
insert_md_section_for__class_methods=insert_md_section_for__class_methods,
|
||||
remove_repeated_sections_classmethods=remove_repeated_sections_classmethods,
|
||||
files_to_include=[],
|
||||
output_name=CALL_REFERENCE_OFILENAME,
|
||||
delete_html_comments=True)
|
||||
log_obj.save()
|
||||
return log_obj.load(**kw), log_obj.load_to_listbox()
|
||||
|
||||
|
||||
@timeit
|
||||
def compile_readme(output_filename='LoG', **kw):
|
||||
''' Compile a "2_readme.md" file'''
|
||||
log_obj = BESTLOG(os.path.join(cd, output_filename))
|
||||
main(logger=log_obj,
|
||||
insert_md_section_for__class_methods=insert_md_section_for__class_methods,
|
||||
remove_repeated_sections_classmethods=remove_repeated_sections_classmethods,
|
||||
files_to_include=[0, 1, 2, 3],
|
||||
output_name=README_OFILENAME,
|
||||
delete_html_comments=True)
|
||||
log_obj.save()
|
||||
return log_obj.load(**kw), log_obj.load_to_listbox()
|
||||
|
||||
def compile_all_stuff(**kw):
|
||||
'''
|
||||
Compile a "2_ and 5_" .md filess
|
||||
return output from them
|
||||
'''
|
||||
return compile_readme(**kw), compile_call_ref(**kw)
|
||||
|
||||
|
||||
########################################
|
||||
# _____ #
|
||||
# | __ \ #
|
||||
# | |__) |__ _ __ _ _ _ __ #
|
||||
# | ___/ _ \| '_ \| | | | '_ \ #
|
||||
# | | | (_) | |_) | |_| | |_) | #
|
||||
# |_| \___/| .__/ \__,_| .__/ #
|
||||
# | | | | #
|
||||
# |_| |_| #
|
||||
########################################
|
||||
|
||||
|
||||
def md2psg(target_text):
|
||||
r'''
|
||||
ib<space>color
|
||||
i italic
|
||||
b bold
|
||||
color = can be word can be color
|
||||
red #ff00111
|
||||
green
|
||||
blue
|
||||
i?b?\s?\w+?
|
||||
|
||||
|
||||
usage
|
||||
*i*a** italic
|
||||
*b*a** bold
|
||||
*ib*a** italic bold
|
||||
*ib red*a** italic bold red
|
||||
*b green*a** bold green
|
||||
|
||||
'This was *I*special** message from *B*him**. And from *Igreen*this** to *Ired*this**'
|
||||
'''
|
||||
|
||||
# format
|
||||
# ======
|
||||
font_norm = ('Mono 12 ') # (*sg.DEFAULT_FONT, 'italic')
|
||||
font_bold = ('Mono 12 italic') # (*sg.DEFAULT_FONT, 'italic')
|
||||
font_italic = ('Mono 12 bold') # (*sg.DEFAULT_FONT, 'bold')
|
||||
|
||||
list_of_Ts = []
|
||||
parts = [i for i in re.compile(r'(\*I?B?[a-z]*?\*[\d\D]*?\*\*)', flags=re.M|re.DOTALL).split(target_text) if i is not None]
|
||||
for index, text in enumerate(parts):
|
||||
if index % 2 == 0:
|
||||
# Normal text
|
||||
|
||||
T_text = text
|
||||
T = sg.T(T_text, size=(len(T_text), 1), pad=(0,0), font=font_norm)
|
||||
else:
|
||||
# SPECIAL format
|
||||
T_parameters = {
|
||||
'font': font_norm
|
||||
}
|
||||
|
||||
|
||||
my_format = text[1:].split('*')[0]
|
||||
|
||||
# ::: italic
|
||||
if 'I' in my_format: T_parameters['font'] = font_italic
|
||||
# ::: bold
|
||||
if 'B' in my_format: T_parameters['font'] = font_bold
|
||||
|
||||
# ::: colors
|
||||
color_left = my_format.replace('I', '').replace('B', '')
|
||||
if color_left: T_parameters['text_color'] = color_left
|
||||
|
||||
# making psg element
|
||||
T_text = '*'.join(text.split('*')[2:-2])
|
||||
T = sg.T(T_text, size=(len(T_text), 1), pad=(0,0), **T_parameters)
|
||||
|
||||
list_of_Ts.append(T)
|
||||
|
||||
return list_of_Ts
|
||||
|
||||
def mini_GUI():
|
||||
my_font = ("Helvetica", 12)
|
||||
my_font2 = ("Helvetica", 12, "bold")
|
||||
my_font3 = ("Helvetica", 15, "bold")
|
||||
my_font4 = ("Mono", 18, "bold")
|
||||
|
||||
def make_tab(word):
|
||||
|
||||
def tabs(*layouts):
|
||||
return sg.TabGroup(
|
||||
[[ sg.Tab(title, lay, key=f'-tab-{word_}-{index}-')
|
||||
for index, (title, word_, lay) in enumerate(layouts)
|
||||
]]
|
||||
)
|
||||
|
||||
return [[
|
||||
sg.Column(layout=[
|
||||
[sg.T('debug', font=my_font, text_color='grey')],
|
||||
[sg.ML(size=(50-15, 15), key=f'-{word}-debug-')],
|
||||
[sg.T('error', font=my_font, text_color='red')],
|
||||
[sg.ML(size=(50-15, 15), key=f'-{word}-error-')],
|
||||
], pad=(0, 0)),
|
||||
sg.T(' '),
|
||||
sg.Column(layout=[
|
||||
[sg.T('warning', font=my_font2)],
|
||||
[sg.ML(size=(70-12, 15), key=f'-{word}-warning-')],
|
||||
[sg.T('info', font=my_font2)],
|
||||
[sg.ML(size=(70-12, 15), key=f'-{word}-info-')],
|
||||
], pad=(0, 0)),
|
||||
|
||||
tabs(
|
||||
('Text', word, [
|
||||
[sg.T('warning info', font=my_font3)]
|
||||
,[sg.ML(size=(110, 30), key=f'-{word}-warning_info-')]
|
||||
]),
|
||||
('Listbox', word, [
|
||||
[sg.T('warning info listbox', font=my_font3)]
|
||||
,[sg.Listbox([], size=(110, 30-1), key=f'-{word}-listbox-', enable_events=True, background_color='#ffccaa')]
|
||||
])
|
||||
)
|
||||
]]
|
||||
|
||||
settings_layout = [
|
||||
[sg.CB('Toggle progressbar', False, enable_events=True, key='toggle_progressbar')],
|
||||
|
||||
[
|
||||
sg.Frame('Text editor', [[ sg.Combo(['pycharm', 'subl'], default_value='subl', enable_events=True, key='_text_editor_combo_') ]] ),
|
||||
sg.Frame('Pycharm path:', [[ sg.I('', size=(40, 1), enable_events=True, key='_PyCharm_path_') ]] )
|
||||
],
|
||||
|
||||
[
|
||||
sg.Frame('⅀∉ Filter "empty tables"', [
|
||||
[sg.T('''This is for filtering stirng, like:''')],
|
||||
[sg.T('''Warning ======= We got empty md_table for "EasyPrintClose"''', font='Mono 8')],
|
||||
[sg.CB('enable', True, key='checkbox_enable_empty_tables_filter', enable_events=True)],
|
||||
[sg.ML('PrintClose\nEasyPrintClose\nmain\ntheme\nRead',
|
||||
size=(30,10), enable_events=True, key='_filter_empty_tables_ml_')]]),
|
||||
sg.Frame('⅀∉ Filter "tkinter class methods"', [
|
||||
[sg.T('''This is for filtering stirng, like:''')],
|
||||
[sg.T('''Please, fix ':return:' in 'SetFocus' IF you want to see 'return' row in 'signature table' ''', font='Mono 8')],
|
||||
[sg.CB('enable', True, enable_events=True, key='checkbox_enable_filter_tkinter_class_methods')],
|
||||
[sg.ML('SetFocus\nSetTooltip\nUpdate\n__init__\nbind\nexpand\nset_cursor\nset_size',
|
||||
size=(30,10), enable_events=True, key='_filter_tkinter_class_methods_')]], visible=not True)
|
||||
]
|
||||
]
|
||||
layout = [[sg.TabGroup([[
|
||||
sg.Tab('readme logs', make_tab('README')),
|
||||
sg.Tab('Call reference logs', make_tab('CALL_REF')),
|
||||
sg.Tab('General settings', settings_layout)
|
||||
]])]]
|
||||
|
||||
# ░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░
|
||||
# ░▒▒▓▓▓ progress bar ▓▓▓▒▒░
|
||||
# ░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░
|
||||
from time import sleep; from math import pi, sin; from itertools import count
|
||||
def next_star():
|
||||
middle = 100/2
|
||||
for i in (int(sin(i*pi/middle)*middle + middle) for i in count()): yield i
|
||||
|
||||
psg_module_path = str(sg).split("' from '")[1][:-2]
|
||||
star_bar = sg.Col([
|
||||
[sg.ProgressBar(max_value=100, orientation='h',
|
||||
key='_star_bar1_', size=(50,5), bar_color=('blue', 'yellow'))],
|
||||
[sg.ProgressBar(max_value=100, orientation='h',
|
||||
key='_star_bar2_', size=(50,5), bar_color=('yellow', 'blue'))],
|
||||
])
|
||||
# guia
|
||||
def empty_line(fontsize=12): return [sg.T('', font=('Mono '+str(fontsize)))]
|
||||
|
||||
|
||||
|
||||
window = sg.Window('We are live! Again! --- ' + 'Completed making {}, {}'.format(os.path.basename(README_OFILENAME), os.path.basename(CALL_REFERENCE_OFILENAME)), [
|
||||
[sg.T(size=(30,1), key='-compile-time-'), star_bar],
|
||||
empty_line(),
|
||||
[*md2psg(f'The *Bmagenta*PySimpleGUI** module being processed is *Imagenta*"{psg_module_path}"**'), sg.B('< open (__init__.py)', key='open_init_file'), sg.B('< open (psg.py)', key='open_psg_file')],
|
||||
# [sg.T(f'The **PySimpleGUI** module being processed is *"{psg_module_path}"*')],
|
||||
empty_line(),
|
||||
[
|
||||
sg.B('Run again (F1)', key='-run-')
|
||||
,sg.Col([
|
||||
[sg.CB('show time in logs (F2)', False, enable_events=True, key='show_time')],
|
||||
[sg.CB('Logs with Color (F3)', True, enable_events=True, key='use_psg_color')],
|
||||
])
|
||||
,sg.Col([
|
||||
empty_line(5),
|
||||
[sg.B('open "db folder"', key='-open_db_folder-')],
|
||||
])
|
||||
|
||||
,sg.Frame('', [[
|
||||
sg.Col([
|
||||
[*md2psg('markdown outputFileName *I*FOR** *B*readme **: ')
|
||||
,sg.I(README_OFILENAME, key='README_OFILE', size=(25, 1))
|
||||
,sg.B('open in explorer', key='open in explorer_readme')
|
||||
,sg.B('open in text editor', key='open file - readme')
|
||||
]
|
||||
|
||||
,[*md2psg('markdown outputFileName *I*FOR** *B*call ref**: ')
|
||||
,sg.I(CALL_REFERENCE_OFILENAME, key='CALL_REF_OFILE', size=(25, 1))
|
||||
,sg.B('open in explorer', key='open in explorer_calref')
|
||||
,sg.B('open in text editor', key='open file - calref')
|
||||
]
|
||||
])
|
||||
]], relief=sg.RELIEF_SUNKEN, border_width=4)
|
||||
]
|
||||
,*layout
|
||||
], resizable=True, finalize=True, location=(0,0), return_keyboard_events = True)
|
||||
|
||||
def update_time_in_GUI():
|
||||
window['-compile-time-'](datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S.%f'))
|
||||
|
||||
def update_compilation_in_psg(values):
|
||||
|
||||
#
|
||||
# ░▒▒▓▓▓▓▓◘ compile ◘▓▓▓▓▓▒▒░
|
||||
#
|
||||
result_readme__for_txt_n_listbox, result_call_ref__for_txt_n_listbox = compile_all_stuff(
|
||||
use_psg_color=values['use_psg_color'],
|
||||
show_time=values['show_time'])
|
||||
result_readme_txt, result_readme_listbox_items = result_readme__for_txt_n_listbox
|
||||
result_call_ref_txt, result_call_ref_listbox_items = result_call_ref__for_txt_n_listbox
|
||||
|
||||
#
|
||||
# ░▒▒▓▓▓▓▓◘ define FILTER functions ◘▓▓▓▓▓▒▒░
|
||||
#
|
||||
badNames = [ i.strip() for i in values['_filter_tkinter_class_methods_'].split('\n') if i.strip()]
|
||||
badNames = '|'.join(badNames)
|
||||
regex_str1 = rf"fix .:return:. in .({badNames})."
|
||||
|
||||
badNames = [ i for i in values['_filter_empty_tables_ml_'].split('\n') if i.strip()]
|
||||
badNames = '|'.join(badNames)
|
||||
regex_str2 = rf'empty md_table for .({badNames}).'
|
||||
|
||||
def is_valid_regex_LogMessage(msg: str):
|
||||
nonlocal regex_str1, regex_str2
|
||||
|
||||
# test 1 - filter tkinter class methods
|
||||
error1_found = False
|
||||
if values['checkbox_enable_filter_tkinter_class_methods'] and ':return:' in msg:
|
||||
error1_found = bool(re.search(regex_str1, msg, flags=re.M|re.DOTALL))
|
||||
|
||||
# test 2 - filter "special empty tables"
|
||||
error2_found = False
|
||||
if values['checkbox_enable_empty_tables_filter'] and 'empty md_table for' in msg:
|
||||
error2_found = bool(re.search(regex_str2, msg, flags=re.M|re.DOTALL))
|
||||
|
||||
return not error1_found and not error2_found
|
||||
def filter_log_messages(messages):
|
||||
if type(messages) is str:
|
||||
return '\n'.join([msg for msg in messages.split('\n') if is_valid_regex_LogMessage(msg)])
|
||||
raise TypeError
|
||||
|
||||
#
|
||||
# ▓▓▓ Update GUI ▓▓▓
|
||||
#
|
||||
# =========== listbox's
|
||||
class ParsingError(object):
|
||||
def __init__(self, log_obj):
|
||||
self.log_obj = log_obj
|
||||
self.text = log_obj['message_text']
|
||||
|
||||
def __str__(self): return self.__repr__()
|
||||
def __repr__(self):
|
||||
'''qwe'''
|
||||
# {
|
||||
# 'message_type': 'info',
|
||||
# 'message_text': 'STARTING',
|
||||
# 'message_time': 2,
|
||||
# 'message_metadata': {}
|
||||
# }
|
||||
|
||||
text = self.log_obj['message_text']
|
||||
metadata = self.log_obj['message_metadata']
|
||||
lineno = ''
|
||||
if 'lineno' in metadata.keys(): lineno = "(line:" + str(metadata['lineno']) + ') '
|
||||
|
||||
return f'{lineno} {text}'
|
||||
|
||||
items1 = [i for i in result_readme_listbox_items if is_valid_regex_LogMessage(i['message_text']) ]
|
||||
items2 = [i for i in result_call_ref_listbox_items if is_valid_regex_LogMessage(i['message_text']) ]
|
||||
window['-README-listbox-']([ ParsingError(i) for i in items1])
|
||||
window['-CALL_REF-listbox-']([ ParsingError(i) for i in items2])
|
||||
|
||||
# =========== multitext's
|
||||
|
||||
def set_it(prefix = 'CALL_REF', messages_obj = result_call_ref_txt):
|
||||
|
||||
t_error, t_warning, t_info, t_debug = ['\n'.join(i) for i in messages_obj[:4]]
|
||||
t_error = filter_log_messages(t_error)
|
||||
t_warning = filter_log_messages(t_warning)
|
||||
t_info = filter_log_messages(t_info)
|
||||
t_debug = filter_log_messages(t_debug)
|
||||
|
||||
window[f'-{prefix}-error-'](t_error)
|
||||
window[f'-{prefix}-warning-'](t_warning)
|
||||
window[f'-{prefix}-info-'](t_info)
|
||||
window[f'-{prefix}-debug-'](t_debug)
|
||||
|
||||
# /// colors warning_info
|
||||
window[f'-{prefix}-warning_info-'].update('')
|
||||
t_warning_info_obj = messages_obj[-1]
|
||||
|
||||
if values['use_psg_color']:
|
||||
for text, color in t_warning_info_obj:
|
||||
if not is_valid_regex_LogMessage(text): continue
|
||||
window[f'-{prefix}-warning_info-'].print(text, text_color=color)
|
||||
else:
|
||||
window[f'-{prefix}-warning_info-'](t_warning_info_obj)
|
||||
|
||||
# two calls
|
||||
set_it('README', result_readme_txt)
|
||||
set_it('CALL_REF', result_call_ref_txt)
|
||||
|
||||
# ~~~~~~~~~~~~
|
||||
# GUI updating
|
||||
# ~~~~~~~~~~~~
|
||||
update_time_in_GUI()
|
||||
|
||||
values = window.read(timeout=0)[1]
|
||||
update_compilation_in_psg(values)
|
||||
p_values = values
|
||||
|
||||
window['_PyCharm_path_'](APP_CONFIGS['_PyCharm_path_'])
|
||||
window['_text_editor_combo_'].update(set_to_index=APP_CONFIGS['_text_editor_combo_']) # index
|
||||
|
||||
window['toggle_progressbar'](APP_CONFIGS['toggle_progressbar'])
|
||||
|
||||
window['checkbox_enable_empty_tables_filter'](APP_CONFIGS['checkbox_enable_empty_tables_filter'])
|
||||
window['_filter_empty_tables_ml_'](APP_CONFIGS['_filter_empty_tables_ml_'])
|
||||
|
||||
window['checkbox_enable_filter_tkinter_class_methods'](APP_CONFIGS['checkbox_enable_filter_tkinter_class_methods'])
|
||||
window['_filter_tkinter_class_methods_'](APP_CONFIGS['_filter_tkinter_class_methods_'])
|
||||
|
||||
window['show_time'](APP_CONFIGS['show_time'])
|
||||
window['use_psg_color'](APP_CONFIGS['use_psg_color'])
|
||||
|
||||
window['README_OFILE'](APP_CONFIGS['README_OFILE'])
|
||||
window['CALL_REF_OFILE'](APP_CONFIGS['CALL_REF_OFILE'])
|
||||
|
||||
|
||||
next_val_gen = next_star()
|
||||
my_timeout = None
|
||||
while True:
|
||||
event, values = window(timeout=my_timeout)
|
||||
if event in ('Exit', None):
|
||||
# save to disk
|
||||
|
||||
# APP_CONFIGS['_PyCharm_path_'] = p_values['_PyCharm_path_']
|
||||
APP_CONFIGS['_text_editor_combo_'] = 1 if window['_text_editor_combo_'].get() == 'subl' else 0
|
||||
|
||||
APP_CONFIGS['toggle_progressbar'] = p_values['toggle_progressbar']
|
||||
|
||||
APP_CONFIGS['checkbox_enable_empty_tables_filter'] = p_values['checkbox_enable_empty_tables_filter']
|
||||
APP_CONFIGS['_filter_empty_tables_ml_'] = p_values['_filter_empty_tables_ml_']
|
||||
|
||||
APP_CONFIGS['checkbox_enable_filter_tkinter_class_methods'] = p_values['checkbox_enable_filter_tkinter_class_methods']
|
||||
APP_CONFIGS['_filter_tkinter_class_methods_'] = p_values['_filter_tkinter_class_methods_']
|
||||
|
||||
APP_CONFIGS['show_time'] = p_values['show_time']
|
||||
APP_CONFIGS['use_psg_color'] = p_values['use_psg_color']
|
||||
|
||||
APP_CONFIGS['README_OFILE'] = p_values['README_OFILE']
|
||||
APP_CONFIGS['CALL_REF_OFILE'] = p_values['CALL_REF_OFILE']
|
||||
|
||||
save_configs(APP_CONFIGS)
|
||||
break
|
||||
p_values = values
|
||||
|
||||
|
||||
if '__TIMEOUT__' in event:
|
||||
if values['toggle_progressbar']:
|
||||
window['_star_bar1_'].UpdateBar(next(next_val_gen))
|
||||
window['_star_bar2_'].UpdateBar(next(next_val_gen))
|
||||
if '__TIMEOUT__' not in event:
|
||||
print('PSG event>', event)
|
||||
|
||||
if event == 'toggle_progressbar':
|
||||
my_timeout = None if not values['toggle_progressbar'] else 100
|
||||
|
||||
if event == '-README-listbox-':
|
||||
metadata = values['-README-listbox-'][0].log_obj['message_metadata']
|
||||
print(f'metadata = {metadata}')
|
||||
|
||||
if event == '-CALL_REF-listbox-':
|
||||
ParsingError_obj = values['-CALL_REF-listbox-'][0]
|
||||
metadata = ParsingError_obj.log_obj['message_metadata']
|
||||
if 'lineno' in metadata.keys():
|
||||
lineno = metadata['lineno']
|
||||
texteditor = values['_text_editor_combo_']
|
||||
psg_module_path_SDK = psg_module_path.replace('__init__.py', 'PySimpleGUI.py')
|
||||
if 'pycharm' == texteditor:
|
||||
texteditor = values['_PyCharm_path_']
|
||||
subprocess.Popen(f'"{texteditor}" --line {lineno} "{psg_module_path_SDK}"', shell=True)
|
||||
elif 'subl' == texteditor:
|
||||
subprocess.Popen(f'{texteditor} "{psg_module_path_SDK}:{lineno}"', shell=True)
|
||||
|
||||
# if event == '-CALL_REF-listbox-':
|
||||
# res = values['-CALL_REF-listbox-'][0]
|
||||
# print(f'res = {res}')
|
||||
|
||||
|
||||
if event == '-run-' or 'F1' in event: update_compilation_in_psg(values)
|
||||
# folder
|
||||
if event == '-open_db_folder-': opendir(cd)
|
||||
# folder
|
||||
if event == 'open in explorer_readme': opendir(os.path.dirname(os.path.join(cd, values['README_OFILE'])))
|
||||
if event == 'open in explorer_calref': opendir(os.path.dirname(os.path.join(cd, values['CALL_REF_OFILE'])))
|
||||
# file
|
||||
if event == 'open file - readme': openfile(os.path.join(cd, values['README_OFILE']))
|
||||
if event == 'open file - calref': openfile(os.path.join(cd, values['CALL_REF_OFILE']))
|
||||
# file
|
||||
if event == 'open_init_file': openfile(psg_module_path)
|
||||
if event == 'open_psg_file': openfile(psg_module_path.replace('__init__.py', 'PySimpleGUI.py'))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# hotkeys
|
||||
if 'F2' in event: window['show_time'](not values['show_time'])
|
||||
if 'F3' in event: window['use_psg_color'](not values['use_psg_color'])
|
||||
|
||||
window.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
mini_GUI()
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
readfile = lambda fpath: open(fpath, 'r', encoding='utf-8').read()
|
||||
writefile = lambda fpath, x: open(fpath, 'w', encoding='utf-8').write(x)
|
||||
|
||||
from collections import Counter
|
||||
asd = Counter(readfile('LoG_call_ref.json').split('\n'))
|
||||
import pdb; pdb.set_trace();
|
|
@ -0,0 +1,61 @@
|
|||
import PySimpleGUIQt as sg
|
||||
print(sg)
|
||||
|
||||
|
||||
dicta1 = {
|
||||
"a": "hellgdfgo world",
|
||||
4: 5,
|
||||
"qwerty" : "ytjyhrewq"
|
||||
}
|
||||
dicta2 = {
|
||||
"a": "helldasdo world",
|
||||
4: 5,
|
||||
"qwerty" : "ytrewq"
|
||||
}
|
||||
dicta3 = {
|
||||
"a": "hello world",
|
||||
4: 5,
|
||||
"qwerty" : "ytwqddqwrewq"
|
||||
}
|
||||
|
||||
|
||||
class ParsingError(object):
|
||||
def __init__(self, psg_object, num):
|
||||
self.num = num
|
||||
self.psg_object = psg_object
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.num} {self.psg_object}'
|
||||
|
||||
@staticmethod
|
||||
def headers():
|
||||
return 'num,psg_object'.split(',')
|
||||
|
||||
|
||||
items = [
|
||||
ParsingError(dicta1, 45),
|
||||
ParsingError(dicta2, 42),
|
||||
ParsingError(dicta3, 12),
|
||||
]
|
||||
|
||||
|
||||
window = sg.Window('Test', [
|
||||
[sg.Listbox(items, key='qwe', enable_events=True)],
|
||||
[sg.B('q1'), sg.B('q2'), sg.B('q3')],
|
||||
],return_keyboard_events=True)
|
||||
|
||||
while True:
|
||||
event, values = window()
|
||||
if event in ('Exit', None): break
|
||||
|
||||
print(event, values)
|
||||
|
||||
if event == 'q1':
|
||||
gui = values['qwe'][0]
|
||||
print(gui.num)
|
||||
print(gui.psg_object[4])
|
||||
|
||||
window.close()
|
|
@ -0,0 +1,60 @@
|
|||
import inspect
|
||||
import PySimpleGUI
|
||||
|
||||
"""
|
||||
Create All Possible Tags
|
||||
Will output to STDOUT all of the different tags for classes, members and functions for a given PySimpleGUI.py
|
||||
file. Functions that begin with _ are filtered out from the list.
|
||||
Displays the results in a PySimpleGUI window which can be used to copy and paste into other places.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def new_name(name):
|
||||
name = name.replace("OK", "*1")
|
||||
name = name.replace("TK", "*2")
|
||||
name = name.replace("RGB", "*3")
|
||||
new = name[0].lower()
|
||||
for c in name[1:]:
|
||||
new += '_' + c.lower() if (c.isupper() or c == "*") else c
|
||||
new=new.replace("*1", "ok")
|
||||
new = new.replace("*2", "tk")
|
||||
new = new.replace("*3", "rgb")
|
||||
return new
|
||||
|
||||
layout = [[PySimpleGUI.Output(size=(600,300))]]
|
||||
window = PySimpleGUI.Window('Dump of tags', layout, resizable=True).Finalize()
|
||||
|
||||
psg_members = inspect.getmembers(PySimpleGUI)
|
||||
|
||||
psg_funcs = [o for o in psg_members if inspect.isfunction(o[1])]
|
||||
psg_classes = [o for o in psg_members if inspect.isclass(o[1])]
|
||||
# I don't know how this magic filtering works, I just know it works. "Private" stuff (begins with _) are somehow
|
||||
# excluded from the list with the following 2 lines of code. Very nicely done Kol-ee-ya!
|
||||
psg_classes_ = list(set([i[1] for i in psg_classes])) # filtering of anything that starts with _ (methods, classes, etc)
|
||||
psg_classes = list(zip([i.__name__ for i in psg_classes_], psg_classes_))
|
||||
|
||||
for pclass in sorted(psg_classes):
|
||||
if 'Tk' in pclass[0] or 'TK' in pclass[0] or 'Element' == pclass[0]: # or 'Window' == i[0]:
|
||||
continue
|
||||
# print(f'### {pclass[0]} Element')
|
||||
# print('')
|
||||
# print(f'<!-- <+{pclass[0]}.doc+> -->')
|
||||
# print(f'<!-- <+{pclass[0]}.__init__+> -->')
|
||||
print('')
|
||||
print(f'{pclass[0]} methods in PEP8 format --------------------------------------')
|
||||
for funcs in inspect.getmembers(pclass[1]):
|
||||
if '_' not in funcs[0]:
|
||||
# print(f'{pclass[0]}.{new_name(funcs[0])} = {pclass[0]}.{funcs[0]}') # version that has class on front
|
||||
print(f'{new_name(funcs[0])} = {funcs[0]}') # version without class on front (use for most)
|
||||
# print('\n'.join([f"#### {j[0]}\n\n<!-- <+{pclass[0]}.{j[0]}+> -->\n" for j in inspect.getmembers(pclass[1]) if '_' not in j[0]]))
|
||||
|
||||
# print('\n------------------------- Functions start here -------------------------\n')
|
||||
#
|
||||
for f in psg_funcs:
|
||||
if f[0][0] == '_':
|
||||
continue
|
||||
print(f'{new_name(f[0])} = {f[0]}')
|
||||
# print(f"<!-- <+func.{f[0]}+> -->")
|
||||
|
||||
window.Read()
|
|
@ -0,0 +1,83 @@
|
|||
import make_real_readme as mk_readme
|
||||
import PySimpleGUI as sg
|
||||
import logging, os
|
||||
|
||||
enable_logs = False
|
||||
|
||||
def readfile(filename):
|
||||
with open(filename, 'r', encoding='utf-8') as ff:
|
||||
return ff.read()
|
||||
|
||||
def writefile(fpath, content):
|
||||
with open(fpath, 'w', encoding='utf-8') as ff:
|
||||
ff.write(content)
|
||||
|
||||
|
||||
window = sg.Window('Test', [
|
||||
[sg.CB('include all .md files', True, key='all-checkbox', enable_events=True)],
|
||||
[sg.CB('1', True, key='file1', disabled=True, enable_events=True)],
|
||||
[sg.CB('2', False,key='file2', disabled=True, enable_events=True)],
|
||||
[sg.CB('3', False,key='file3', disabled=True, enable_events=True)],
|
||||
|
||||
|
||||
[sg.CB('4', False,key='file4', disabled=True, enable_events=True)],
|
||||
[sg.T('well, this is you output name:'), sg.I('readme.md', key='output_name'), sg.B('aaaand, hope for the best... Compile.', key='comp')],
|
||||
|
||||
[sg.T('-- -- -- -- -- -- --\nlogs\n-- -- -- -- -- -- --', justification='center')],
|
||||
# [sg.ML('', key='logs', size=(120,25))]
|
||||
|
||||
[sg.Column([
|
||||
[sg.T('COMPLETE')],
|
||||
[sg.Listbox([], size=(40, 10), key='COMPLETE')]
|
||||
]), sg.Column([
|
||||
[sg.T('NOTCOMPLETE')],
|
||||
[sg.Listbox([], size=(40, 10), key='NOTCOMPLETE')]
|
||||
])],
|
||||
|
||||
],element_justification='center')
|
||||
|
||||
while True:
|
||||
event, values = window()
|
||||
if event in ('Exit', None): break
|
||||
|
||||
print(event, values)
|
||||
|
||||
if event == 'all-checkbox':
|
||||
window['file1'](disabled=values['all-checkbox'])
|
||||
window['file2'](disabled=values['all-checkbox'])
|
||||
window['file3'](disabled=values['all-checkbox'])
|
||||
window['file4'](disabled=values['all-checkbox'])
|
||||
if event == 'comp':
|
||||
|
||||
if enable_logs: window['logs'](values['logs'] + 'start')
|
||||
|
||||
# MAIN WORK - START
|
||||
# 1### logging module
|
||||
logger = logging.getLogger(__name__); logger.setLevel(logging.DEBUG); a_log_file = logging.FileHandler('_logs.txt', mode='w'); a_log_file.setLevel(logging.DEBUG); formatter = logging.Formatter('%(asctime)s>%(levelname)s: %(message)s'); a_log_file.setFormatter(formatter); logger.addHandler(a_log_file);
|
||||
# 2### files to compile
|
||||
files = [0, 1, 2, 3] if values['all-checkbox'] else []
|
||||
if not values['all-checkbox']:
|
||||
if values['file1']: files.append(1)
|
||||
if values['file2']: files.append(2)
|
||||
if values['file3']: files.append(3)
|
||||
if values['file4']: files.append(4)
|
||||
# 3### REAL work:
|
||||
mk_readme.main(logger=logger,
|
||||
files_to_include=files,
|
||||
output_name=values['output_name'],
|
||||
delete_html_comments=True)
|
||||
|
||||
# MAIN WORK - END
|
||||
|
||||
for i in readfile('_logs.txt').split('\n'):
|
||||
if i.endswith('--> - COMPLETE'):
|
||||
window['COMPLETE'](values['COMPLETE'] + [i])
|
||||
else:
|
||||
window['NOTCOMPLETE'](values['COMPLETE'] + [i])
|
||||
|
||||
if enable_logs: window['logs'](values['logs'] + output_readme)
|
||||
|
||||
|
||||
|
||||
|
||||
window.close()
|
|
@ -1,11 +1,9 @@
|
|||
import inspect
|
||||
from inspect import getmembers, isfunction, isclass, getsource, signature, _empty, isdatadescriptor
|
||||
from datetime import datetime
|
||||
import click, textwrap, logging, json, re, os
|
||||
import PySimpleGUI, click, textwrap, logging, json, re, os
|
||||
import os
|
||||
cd = CD = os.path.dirname(os.path.abspath(__file__))
|
||||
import PySimpleGUI as sg
|
||||
|
||||
module_to_process = sg
|
||||
|
||||
from collections import namedtuple
|
||||
triplet = namedtuple('triplet', 'name value atype'.split(' '))
|
||||
|
@ -39,6 +37,9 @@ TABLE_Only_table_RETURN_TEMPLATE = '''|Type|Name|Meaning|\n|---|---|---|\n|<type
|
|||
from collections import namedtuple
|
||||
special_case = namedtuple('special_case', 'ok sig table just_text'.split(' '))
|
||||
|
||||
def get_line_number(python_obj):
|
||||
return inspect.getsourcelines(python_obj)[1]
|
||||
|
||||
|
||||
"""
|
||||
injection_points:
|
||||
|
@ -83,22 +84,26 @@ CLASS
|
|||
}
|
||||
"""
|
||||
|
||||
def get_return_part(code: str, line_break=None) -> str:
|
||||
def get_return_part(code: str, line_break=None):
|
||||
""" Find ":return:" part in given "doc string"."""
|
||||
if not line_break:
|
||||
# line_break = ' <br> '
|
||||
line_break = ''
|
||||
|
||||
if ':return:' not in code:
|
||||
return ''
|
||||
return '', ''
|
||||
|
||||
|
||||
|
||||
|
||||
only_return = code[code.index(':return:')+len(':return:'):].strip().replace('\n', line_break)
|
||||
if ':rtype' in only_return:
|
||||
only_return = only_return.split(':rtype')[0]
|
||||
return only_return
|
||||
only_return = only_return.split(':rtype')[0].strip()
|
||||
|
||||
return_TYPE = ''
|
||||
if ':rtype' in code:
|
||||
rcode = code.strip()
|
||||
return_TYPE = rcode[rcode.index(':rtype:')+len(':rtype:'):].strip()
|
||||
return only_return, return_TYPE
|
||||
|
||||
|
||||
def special_cases(function_name, function_obj, sig, doc_string, line_break=None):
|
||||
|
@ -144,10 +149,17 @@ def special_cases(function_name, function_obj, sig, doc_string, line_break=None)
|
|||
return special_case(ok=True, just_text=f'\n\n#### property: {function_name}\n{get_doc_desc(doca, function_obj)}\n\n', sig='', table='')
|
||||
# TEMPLATE3
|
||||
elif only_self and doca and ':param' not in doca and ':return:' in doca:
|
||||
return_part, desc = get_return_part(doca, line_break=line_break), get_doc_desc(doca, function_obj)
|
||||
return special_case(ok=True, just_text='',
|
||||
return_part, return_part_type = get_return_part(doca, line_break=line_break)
|
||||
# print(return_part, return_part_type)
|
||||
desc = get_doc_desc(doca, function_obj)
|
||||
|
||||
a_table = TABLE_Only_table_RETURN_TEMPLATE.replace('$', return_part) + '\n\n'
|
||||
if return_part_type:
|
||||
a_table = a_table.replace('<type>', return_part_type)
|
||||
|
||||
return special_case(ok=True, just_text='',
|
||||
sig=f'\n\n#### property: {function_name}\n{desc}\n\n',
|
||||
table=TABLE_Only_table_RETURN_TEMPLATE.replace('$', return_part) + '\n\n')
|
||||
table=a_table)
|
||||
|
||||
################################################################################################################
|
||||
# _ _ _ _ _ #
|
||||
|
@ -161,27 +173,27 @@ def special_cases(function_name, function_obj, sig, doc_string, line_break=None)
|
|||
################################################################################################################
|
||||
|
||||
"""
|
||||
# TEMPLATE1
|
||||
# TEMPLATE1
|
||||
|
||||
def Get(self):
|
||||
''' '''
|
||||
# TEMPLATE2 -return -param
|
||||
def Get(self):
|
||||
'''
|
||||
blah blah blah
|
||||
'''
|
||||
# TEMPLATE3 +return -param
|
||||
def Get(self):
|
||||
'''
|
||||
blah blah blah
|
||||
:return: blah-blah
|
||||
'''
|
||||
# TEMPLATE4 -return +param
|
||||
def SetFocus(self, elem):
|
||||
'''
|
||||
blah blah blah
|
||||
:param elem: qwerty
|
||||
'''
|
||||
def Get(self):
|
||||
''' '''
|
||||
# TEMPLATE2 -return -param
|
||||
def Get(self):
|
||||
'''
|
||||
blah blah blah
|
||||
'''
|
||||
# TEMPLATE3 +return -param
|
||||
def Get(self):
|
||||
'''
|
||||
blah blah blah
|
||||
:return: blah-blah
|
||||
'''
|
||||
# TEMPLATE4 -return +param
|
||||
def SetFocus(self, elem):
|
||||
'''
|
||||
blah blah blah
|
||||
:param elem: qwerty
|
||||
'''
|
||||
"""
|
||||
|
||||
# TEMPLATE1
|
||||
|
@ -192,9 +204,14 @@ def special_cases(function_name, function_obj, sig, doc_string, line_break=None)
|
|||
return special_case(ok=True, just_text=f'\n\n{doca}\n\n```python\n{function_name}()\n```\n\n', sig='', table='')
|
||||
# TEMPLATE3
|
||||
elif only_self and doca and ':param' not in doca and ':return:' in doca:
|
||||
return_part, desc = get_return_part(doca, line_break=line_break), get_doc_desc(doca, function_obj)
|
||||
return_part, return_part_type = get_return_part(doca, line_break=line_break)
|
||||
desc = get_doc_desc(doca, function_obj)
|
||||
|
||||
a_table = TABLE_Only_table_RETURN_TEMPLATE.replace('$', return_part) + '\n\n'
|
||||
if return_part_type:
|
||||
a_table = a_table.replace('<type>', return_part_type)
|
||||
return special_case(ok=True, just_text='', sig=f'\n\n{desc}\n\n`{function_name}()`\n\n',
|
||||
table=TABLE_Only_table_RETURN_TEMPLATE.replace('$', return_part) + '\n\n')
|
||||
table=a_table)
|
||||
# TEMPLATE4
|
||||
elif only_self and doca and ':param' not in doca and ':return:' in doca:
|
||||
return special_case(ok=False, just_text='', sig='', table='')
|
||||
|
@ -227,7 +244,8 @@ def is_propery(func):
|
|||
|
||||
def get_sig_table_parts(function_obj, function_name, doc_string,
|
||||
logger=None, is_method=False, line_break=None,
|
||||
insert_md_section_for__class_methods=False):
|
||||
insert_md_section_for__class_methods=False,
|
||||
replace_pipe_bar_in_TYPE_TEXT_char=''):
|
||||
"""
|
||||
Convert python object "function + __doc__"
|
||||
to
|
||||
|
@ -245,6 +263,7 @@ def get_sig_table_parts(function_obj, function_name, doc_string,
|
|||
if logger: logger.error(f'PROBLEM WITH "{function_obj}" "{function_name}":\nit\'s signature is BS. Ok, I will just return \'\' for \'signature\' and \'param_table\'\nOR BETTER - delete it from the 2_readme.md.\n======')
|
||||
return '', ''
|
||||
|
||||
# if 'The text currently displayed on the butto' in
|
||||
|
||||
if not is_propery(function_obj):
|
||||
for key in sig:
|
||||
|
@ -252,22 +271,23 @@ def get_sig_table_parts(function_obj, function_name, doc_string,
|
|||
if 'self' == str(key): continue
|
||||
elif key == 'args': rows.append('args=*<1 or N object>')
|
||||
elif val == _empty: rows.append(key)
|
||||
elif val == None: rows.append(f'{key}=None')
|
||||
elif type(val) in (int, float): rows.append(f'{key}={val}')
|
||||
elif type(val) is str: rows.append(f'{key}="{val}"')
|
||||
elif type(val) is tuple: rows.append(f'{key}={val}')
|
||||
elif type(val) is bool: rows.append(f'{key}={val}')
|
||||
elif type(val) is bytes: rows.append(f'{key}=...')
|
||||
elif val == None: rows.append(f'{key} = None')
|
||||
elif type(val) in (int, float): rows.append(f'{key} = {val}')
|
||||
elif type(val) is str: rows.append(f'{key} = "{val}"')
|
||||
elif type(val) is tuple: rows.append(f'{key} = {val}')
|
||||
elif type(val) is bool: rows.append(f'{key} = {val}')
|
||||
elif type(val) is bytes: rows.append(f'{key} = ...')
|
||||
else:
|
||||
raise Exception(f'IDK this type -> {key, val}')
|
||||
|
||||
|
||||
# if 'update' in function_name.lower(): breakpoint();
|
||||
|
||||
sig_content = f',\n{TAB_char}'.join(rows) if len(rows) > 2 else f', '.join(rows) if rows else ''
|
||||
|
||||
sign = "\n\n{0}\n\n```\n{1}({2})\n```".format(get_doc_desc(doc_string, function_obj), function_name, sig_content)
|
||||
|
||||
|
||||
|
||||
if is_method:
|
||||
if insert_md_section_for__class_methods:
|
||||
sign = "\n\n{0}\n\n```\n{1}({2})\n```".format(get_doc_desc(doc_string, function_obj), function_name, sig_content)
|
||||
|
@ -287,7 +307,7 @@ def get_sig_table_parts(function_obj, function_name, doc_string,
|
|||
# qpqpqpqpqpqpqpqpqpqpqpqpqpqpqpqpqpqpqpqpqpqpqpqpqp
|
||||
|
||||
# 1
|
||||
return_guy = get_return_part(doc_string, line_break=line_break)
|
||||
return_guy, return_guy_type = get_return_part(doc_string, line_break=line_break)
|
||||
if not return_guy:
|
||||
md_return = return_guy = ''
|
||||
else:
|
||||
|
@ -311,35 +331,56 @@ def get_sig_table_parts(function_obj, function_name, doc_string,
|
|||
(str) -> str
|
||||
Union[str, Tuple[str, int]] -> Union[str, Tuple[str, int]]
|
||||
'''
|
||||
final_txt = ''
|
||||
if re.compile(r'\(\s?\w+\s?\)', flags=re.M|re.DOTALL).match(txt):
|
||||
return txt.rstrip(')').lstrip('(')
|
||||
final_txt = txt.rstrip(')').lstrip('(')
|
||||
else:
|
||||
return txt
|
||||
final_txt = txt
|
||||
|
||||
if ') or (' in final_txt:
|
||||
final_txt = final_txt.replace(') or (', ' OR ')
|
||||
|
||||
if replace_pipe_bar_in_TYPE_TEXT_char and '|' in final_txt:
|
||||
final_txt = final_txt.replace('|', replace_pipe_bar_in_TYPE_TEXT_char)
|
||||
|
||||
return final_txt
|
||||
|
||||
# if 'led by application to change the tooltip text for an Element. Normally invoked using ' in docstring:
|
||||
# pass
|
||||
# print(123)
|
||||
|
||||
|
||||
# |> find PARAM, PARAM_TYPE, PARAM_DESCRIPTIONe
|
||||
# |> find PARAM, PARAM_TYPE, PARAM_DESCRIPTION
|
||||
trips = [triplet( i.group(1), replace_re(i.group(2), r'\s{2,}', ' '), process_type(i.group(3).strip()))
|
||||
for index, i in enumerate(re.finditer(row_n_type_regex, docstring + ' \n'))]
|
||||
if not trips:
|
||||
if not trips and ':return:' not in docstring: # no :param in doc
|
||||
raise Exception('no _TRIPs found!')
|
||||
|
||||
# ===|> format markdown table
|
||||
# ---------------------------
|
||||
|
||||
# ROW template:
|
||||
max_type_width, max_name_width = 20, 20
|
||||
max_type_width, max_name_width = 40, 40
|
||||
try:
|
||||
max_type_width, max_name_width = max([len(i.atype) for i in trips]), max([len(i.name) for i in trips])
|
||||
if trips:
|
||||
max_type_width, max_name_width = max([len(i.atype) for i in trips]), max([len(i.name) for i in trips])
|
||||
except Exception as e:
|
||||
logger.warning(f"ALERT ------ bug with max_type_width, max_name_width variables")
|
||||
logger.debug(f"just ALERT ------ bug with max_type_width, max_name_width variables: {a_original_obj.__name__}")
|
||||
|
||||
# print(f"ALERT ------ bug with max_type_width, max_name_width variables")
|
||||
# import pdb; pdb.set_trace();
|
||||
|
||||
row_template = f'| {{: ^{max_type_width}}} | {{: ^{max_name_width}}} | {{}} |'
|
||||
|
||||
# rows, and finally table.
|
||||
rows = [row_template.format(i.atype, i.name, i.value) for i in trips]
|
||||
rows = []
|
||||
for some_triplet in trips:
|
||||
if '|' in some_triplet.atype:
|
||||
good_atype = some_triplet.atype.replace('|', 'or')
|
||||
else:
|
||||
good_atype = some_triplet.atype
|
||||
good_atype = good_atype.replace(' OR ', ' or ').replace('\\or', 'or')
|
||||
rows.append(row_template.format(good_atype, some_triplet.name, some_triplet.value))
|
||||
|
||||
|
||||
row_n_type_regex = re.compile(r':param ([\d\w\*\s]+):([\d\D]*?):type [\w\d]+:([\d\D].*?)\n', flags=re.M|re.DOTALL)
|
||||
|
||||
|
@ -350,15 +391,22 @@ def get_sig_table_parts(function_obj, function_name, doc_string,
|
|||
a_doc = docstring + ' \n'
|
||||
aa = list(re.finditer(regex_pattern, a_doc))[0]
|
||||
text, atype = aa.group(1).strip(), aa.group(2).strip()
|
||||
|
||||
rows.append(f'| {atype} | **RETURN** | {text}')
|
||||
if text.strip():
|
||||
if '|' in atype:
|
||||
atype_no_pipes = atype.replace('|', 'or')
|
||||
rows.append(f'| {atype_no_pipes} | **RETURN** | {text}')
|
||||
else:
|
||||
rows.append(f'| {atype} | **RETURN** | {text}')
|
||||
except Exception as e:
|
||||
padded_name = "{: <25}".format(f"'{a_original_obj.__name__}'")
|
||||
# TODO - Mike changed this!
|
||||
# logger.warning(f"ALERT ------ Hi, Mike! Please, fix ':return:' in {padded_name}"
|
||||
# " \tIF you want to see 'return' row in 'signature table'")
|
||||
|
||||
# print(a_original_obj)
|
||||
# import pdb; pdb.set_trace();
|
||||
|
||||
pass
|
||||
# func_or_method_name = a_original_obj.__name__.lower()
|
||||
# if True or func_or_method_name not in ['__init__', 'setfocus', 'settooltip', 'update', 'unbind', 'setfocus', 'bind', 'unbind', 'set_size', 'expand', 'set_cursor']:
|
||||
# padded_name = "{: <25}".format(f"'{a_original_obj.__name__}'")
|
||||
# logger.warning(f"ALERT ------ Warning=== Please, fix ':return:' in {padded_name}" +
|
||||
# " \tIF you want to see 'return' row in 'signature table'", metadata={'lineno' : get_line_number(a_original_obj)})
|
||||
|
||||
header = '\nParameter Descriptions:\n\n|Type|Name|Meaning|\n|--|--|--|\n'
|
||||
|
||||
|
@ -371,17 +419,24 @@ def get_sig_table_parts(function_obj, function_name, doc_string,
|
|||
|
||||
# 3
|
||||
try:
|
||||
# if 'list of valid string' in doc_string:
|
||||
# import pdb; pdb.set_trace();
|
||||
|
||||
params_TABLE = md_table = make_md_table_from_docstring(doc_string, function_obj)
|
||||
except Exception as e:
|
||||
logger.warning(f'Boy======= We got empty md_table for "{function_obj.__name__}"')
|
||||
func_name_ = function_obj.__name__
|
||||
if func_name_ not in ['unbind', 'theme_'] and not func_name_.startswith('theme_'):
|
||||
logger.warning(f'Warning======= We got empty md_table for "{func_name_}"',
|
||||
metadata={'lineno' : get_line_number(function_obj)})
|
||||
params_TABLE = md_table = ''
|
||||
|
||||
if not md_table.strip():
|
||||
params_TABLE = ''
|
||||
|
||||
if return_guy:
|
||||
# import pdb; pdb.set_trace();
|
||||
|
||||
sign = sign[:-4] + f' -> {return_guy}\n```\n'
|
||||
|
||||
return sign, params_TABLE
|
||||
|
||||
|
||||
|
@ -389,7 +444,7 @@ def pad_n(text):
|
|||
return f'\n{text}\n'
|
||||
|
||||
|
||||
def render(injection, logger=None, line_break=None, insert_md_section_for__class_methods=False):
|
||||
def render(injection, logger=None, line_break=None, insert_md_section_for__class_methods=False, replace_pipe_bar_in_TYPE_TEXT_char=''):
|
||||
|
||||
try:
|
||||
if 'skip readme' in injection['function_object'].__doc__:
|
||||
|
@ -401,14 +456,15 @@ def render(injection, logger=None, line_break=None, insert_md_section_for__class
|
|||
sig, table = get_sig_table_parts(function_obj=injection['function_object'],
|
||||
function_name=injection['part2'],
|
||||
insert_md_section_for__class_methods=insert_md_section_for__class_methods,
|
||||
doc_string=injection['function_object'].__doc__, logger=logger, line_break=line_break)
|
||||
doc_string=injection['function_object'].__doc__, logger=logger, line_break=line_break,
|
||||
replace_pipe_bar_in_TYPE_TEXT_char=replace_pipe_bar_in_TYPE_TEXT_char)
|
||||
else: # class method
|
||||
function_name = injection['parent_class'].__name__ if injection['part2'] == '__init__' else injection['part2']
|
||||
sig, table = get_sig_table_parts(function_obj=injection['function_object'],
|
||||
function_name=function_name, is_method=True,
|
||||
insert_md_section_for__class_methods=insert_md_section_for__class_methods,
|
||||
doc_string=injection['function_object'].__doc__, logger=logger, line_break=line_break)
|
||||
|
||||
doc_string=injection['function_object'].__doc__, logger=logger, line_break=line_break,
|
||||
replace_pipe_bar_in_TYPE_TEXT_char=replace_pipe_bar_in_TYPE_TEXT_char)
|
||||
|
||||
if injection['number'] == '':
|
||||
return pad_n(sig) + pad_n(table)
|
||||
|
@ -425,6 +481,7 @@ def readfile(fname):
|
|||
return ff.read()
|
||||
|
||||
|
||||
|
||||
def main(do_full_readme=False,
|
||||
files_to_include: list = [],
|
||||
logger:object=None,
|
||||
|
@ -437,7 +494,8 @@ def main(do_full_readme=False,
|
|||
remove_repeated_sections_classmethods:bool=False,
|
||||
output_repeated_tags:bool=False,
|
||||
main_md_file='markdown input files/2_readme.md',
|
||||
skip_dunder_method:bool=True):
|
||||
skip_dunder_method:bool=True, verbose = False,
|
||||
replace_pipe_bar_in_TYPE_TEXT_char=''):
|
||||
"""
|
||||
Goal is:
|
||||
1) load 1_.md 2_.md 3_.md 4_.md
|
||||
|
@ -483,8 +541,9 @@ def main(do_full_readme=False,
|
|||
return True
|
||||
|
||||
|
||||
psg_members = [i for i in getmembers(module_to_process) if valid_field(i)] # variables, functions, classes
|
||||
# psg_members = getmembers(PySimpleGUIlib) # variables, functions, classes
|
||||
if verbose: timee(''' psg_members ''')
|
||||
psg_members = [i for i in getmembers(PySimpleGUI) if valid_field(i)] # variables, functions, classes
|
||||
# psg_members = getmembers(PySimpleGUI) # variables, functions, classes
|
||||
psg_funcs = [o for o in psg_members if isfunction(o[1])] # only functions
|
||||
psg_classes = [o for o in psg_members if isclass(o[1])] # only classes
|
||||
psg_classes_ = list(set([i[1] for i in psg_classes])) # boildown B,Btn,Butt -into-> Button
|
||||
|
@ -514,6 +573,7 @@ def main(do_full_readme=False,
|
|||
|
||||
# >1 REMOVE HEADER
|
||||
|
||||
if verbose: timee(''' REMOVE HEADER ''')
|
||||
started_mark = '<!-- Start from here -->'
|
||||
if started_mark in readme:
|
||||
readme = readme.split('<!-- Start from here -->')[1]
|
||||
|
@ -521,6 +581,7 @@ def main(do_full_readme=False,
|
|||
|
||||
|
||||
# 2> find good tags
|
||||
if verbose: timee(''' find good tags ''')
|
||||
re_tags = re.compile(r'<!-- <\+[a-zA-Z_]+[\d\w_]*\.([a-zA-Z_]+[\d\w_]*)\+> -->')
|
||||
mark_points = [i for i in readme.split('\n') if re_tags.match(i)]
|
||||
|
||||
|
@ -534,6 +595,7 @@ def main(do_full_readme=False,
|
|||
readme = readme.replace(i, '\n')
|
||||
|
||||
# 4> log repeated tags
|
||||
if verbose: timee(''' log repeated tags ''')
|
||||
if output_repeated_tags:
|
||||
if not allow_multiple_tags and len(list(set(mark_points))) != len(mark_points):
|
||||
mark_points_copy = mark_points[:]
|
||||
|
@ -555,6 +617,7 @@ def main(do_full_readme=False,
|
|||
|
||||
|
||||
|
||||
if verbose: timee('''# 0===0 functions 0===0''')
|
||||
# 0===0 functions 0===0
|
||||
for tag in func_tags:
|
||||
|
||||
|
@ -592,6 +655,7 @@ def main(do_full_readme=False,
|
|||
logger.error(f' General error in parsing function tag: tag = "{tag}"; error="{str(e)}"')
|
||||
continue
|
||||
|
||||
if verbose: timee('''# 0===0 classes 0===0''')
|
||||
injection_points.append('now, classes.')
|
||||
# 0===0 classes 0===0
|
||||
for tag in classes_method_tags:
|
||||
|
@ -648,35 +712,38 @@ def main(do_full_readme=False,
|
|||
# =========== 5 injecting =========== #
|
||||
# 888888888888888888888888888888888888888
|
||||
# PLAN:
|
||||
# (1) replace tags in 2_readme
|
||||
# (1) replace tags in main_md_file
|
||||
# with properly formateed text
|
||||
# (2) log some data
|
||||
# 8888888888888888888888888888888888888888888888888888888
|
||||
|
||||
|
||||
bar_it = lambda x: '\n' + '='*len(x) + f'\nSTARTING TO INSERT markdown text into 2_readme.md\n' + '='*len(x) + '\n'
|
||||
if verbose: timee('''bar_it = lambda x''')
|
||||
bar_it = lambda x: '\n' + '='*len(x) + f'\nSTARTING TO INSERT markdown text into main_md_file\n' + '='*len(x) + '\n'
|
||||
# 1> log some data
|
||||
success_tags = []
|
||||
bad_tags = []
|
||||
for injection in injection_points:
|
||||
if injection == 'now, classes.':
|
||||
logger.info(bar_it('STARTING TO INSERT markdown text into 2_readme.md'))
|
||||
logger.info(bar_it('STARTING TO INSERT markdown text into main_md_file'))
|
||||
continue
|
||||
|
||||
# SPECIAL CASE: X.doc tag
|
||||
if injection['part2'] == 'doc':
|
||||
a_tag = injection['tag']
|
||||
logger.info(f'a_tag = {a_tag, type(a_tag).__name__}')
|
||||
# logger.info(f'a_tag = {a_tag.split('.')[0].split('+')[1], type(a_tag).__name__}')
|
||||
logger.info('a_tag = ' + a_tag.split('.')[0].split('+')[1])
|
||||
doc_ = '' if not injection['parent_class'].__doc__ else injection['parent_class'].__doc__
|
||||
# if doc_ == None or a_tag == None:
|
||||
|
||||
|
||||
readme = readme.replace(a_tag, doc_)
|
||||
|
||||
else:
|
||||
|
||||
if verbose: timee('''content = render''')
|
||||
content = render(injection, logger=logger, line_break=line_break,
|
||||
insert_md_section_for__class_methods=insert_md_section_for__class_methods)
|
||||
insert_md_section_for__class_methods=insert_md_section_for__class_methods,
|
||||
replace_pipe_bar_in_TYPE_TEXT_char=replace_pipe_bar_in_TYPE_TEXT_char)
|
||||
if verbose: timee('''content = render end''')
|
||||
|
||||
tag = injection["tag"]
|
||||
if content:
|
||||
|
@ -687,6 +754,7 @@ def main(do_full_readme=False,
|
|||
readme = readme.replace(tag, content)
|
||||
|
||||
|
||||
if verbose: timee('''readme = readme.replace(bad_p''')
|
||||
bad_part = '''\n\nParameter Descriptions:\n\n|Type|Name|Meaning|\n|--|--|--|\n\n'''
|
||||
readme = readme.replace(bad_part, '\n')
|
||||
|
||||
|
@ -713,6 +781,7 @@ def main(do_full_readme=False,
|
|||
|
||||
|
||||
|
||||
if verbose: timee('''files = []''')
|
||||
files = []
|
||||
if 0 in files_to_include: files.append(readfile('markdown input files/1_HEADER_top_part.md'))
|
||||
if 1 in files_to_include: files.append(readme)
|
||||
|
@ -721,6 +790,7 @@ def main(do_full_readme=False,
|
|||
|
||||
Joined_MARKDOWN = '\n\n'.join(files) if do_full_readme or files else readme
|
||||
|
||||
if verbose: timee('''if output_name:''')
|
||||
if output_name:
|
||||
with open(output_name, 'w', encoding='utf-8') as ff:
|
||||
CURR_DT = datetime.today().strftime('<!-- CREATED: %Y-%m-%d %H.%M.%S -->\n')
|
||||
|
|
|
@ -25,7 +25,11 @@ HOW DO I INSERT IMAGES ???
|
|||
|
||||
-->
|
||||
|
||||

|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/PySimpleGUI/PySimpleGUI/master/images/for_readme/Logo%20with%20text%20for%20GitHub%20Top.png" alt="Python GUIs for Humans">
|
||||
<h2 align="center">Python GUIs for Humans</h2>
|
||||
</p>
|
||||
|
||||
[](http://pepy.tech/project/pysimplegui) tkinter
|
||||
[](https://pepy.tech/project/pysimplegui27) tk 2.7
|
||||
|
@ -1198,7 +1202,7 @@ Like above, you may have to install either pip or tkinter. To do this on Python
|
|||
|
||||
`sudo apt install python-tkinter`
|
||||
|
||||
### Upgrading from GitHub Using PySimpleGUI
|
||||
## Upgrading from GitHub Using PySimpleGUI
|
||||
|
||||
Starting in version 4.17.0 there is code in the PySimpleGUI package that upgrades your previously pip installed package using the latest version checked into GitHub.
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ Preview of popups:
|
|||
<img src="https://user-images.githubusercontent.com/13696193/44957595-9e15da00-aea1-11e8-8909-6b6121b74509.jpg">
|
||||
</p>
|
||||
|
||||
<!-- <+func.Popup+> -->
|
||||
<!-- <+func.popup+> -->
|
||||
|
||||
The other output Popups are variations on parameters. Usually the button_type parameter is the primary one changed.
|
||||
|
||||
|
@ -996,15 +996,96 @@ You have a couple of options for dealing this with. If your operation can be br
|
|||
|
||||
If, on the other hand, your operation is not under your control or you are unable to add `Refresh` calls, then the next option available to you is to move your long operations into a thread.
|
||||
|
||||
### The "Old Way"
|
||||
|
||||
There are a couple of demo programs available for you to see how to do this. You basically put your work into a thread. When the thread is completed, it tells the GUI by sending a message through a queue. The event loop will run with a timer set to a value that represents how "responsive" you want your GUI to be to the work completing.
|
||||
|
||||
These 2 demo programs are called
|
||||
|
||||
### The "New Way" - `Window.write_event_value`
|
||||
|
||||
This new function that is available currently only in the tkinter port as of July 2020 is exciting and represents the future way multi-threading will be handled in PySimpleGUI (or so is hoped).
|
||||
|
||||
Previously, a queue was used where your event loop would **poll** for incoming messages from a thread.
|
||||
|
||||
Now, threads can directly inject events into a Window so that it will show up in the `window.read()` calls. This allows a your event loop to "pend", waiting for normal window events as well as events being generated by threads.
|
||||
|
||||
You can see this new capability in action in this demo: Demo_Multithreaded_Write_Event_Value.py
|
||||
|
||||
Here is that program for your inspection and education. It's SO nice to no longer poll for threaded events.
|
||||
|
||||
```python
|
||||
Demo_Threaded_Work.py - Best documented. Single thread used for long task
|
||||
Demo_Multithreaded_Long_Tasks.py - Similar to above, but with less fancy GUI. Allows you to set amount of time
|
||||
import threading
|
||||
import time
|
||||
import PySimpleGUI as sg
|
||||
|
||||
|
||||
"""
|
||||
Threaded Demo - Uses Window.write_event_value communications
|
||||
|
||||
Requires PySimpleGUI.py version 4.25.0 and later
|
||||
|
||||
This is a really important demo to understand if you're going to be using multithreading in PySimpleGUI.
|
||||
|
||||
Older mechanisms for multi-threading in PySimpleGUI relied on polling of a queue. The management of a communications
|
||||
queue is now performed internally to PySimpleGUI.
|
||||
|
||||
The importance of using the new window.write_event_value call cannot be emphasized enough. It will hav a HUGE impact, in
|
||||
a positive way, on your code to move to this mechanism as your code will simply "pend" waiting for an event rather than polling.
|
||||
|
||||
Copyright 2020 PySimpleGUI.org
|
||||
"""
|
||||
|
||||
THREAD_EVENT = '-THREAD-'
|
||||
|
||||
cp = sg.cprint
|
||||
|
||||
def the_thread(window):
|
||||
"""
|
||||
The thread that communicates with the application through the window's events.
|
||||
|
||||
Once a second wakes and sends a new event and associated value to the window
|
||||
"""
|
||||
i = 0
|
||||
while True:
|
||||
time.sleep(1)
|
||||
window.write_event_value('-THREAD-', (threading.current_thread().name, i)) # Data sent is a tuple of thread name and counter
|
||||
cp('This is cheating from the thread', c='white on green')
|
||||
i += 1
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
The demo will display in the multiline info about the event and values dictionary as it is being
|
||||
returned from window.read()
|
||||
Every time "Start" is clicked a new thread is started
|
||||
Try clicking "Dummy" to see that the window is active while the thread stuff is happening in the background
|
||||
"""
|
||||
|
||||
layout = [ [sg.Text('Output Area - cprint\'s route to here', font='Any 15')],
|
||||
[sg.Multiline(size=(65,20), key='-ML-', autoscroll=True, reroute_stdout=True, write_only=True, reroute_cprint=True)],
|
||||
[sg.T('Input so you can see data in your dictionary')],
|
||||
[sg.Input(key='-IN-', size=(30,1))],
|
||||
[sg.B('Start A Thread'), sg.B('Dummy'), sg.Button('Exit')] ]
|
||||
|
||||
window = sg.Window('Window Title', layout, finalize=True)
|
||||
|
||||
while True: # Event Loop
|
||||
event, values = window.read()
|
||||
cp(event, values)
|
||||
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||
break
|
||||
if event.startswith('Start'):
|
||||
threading.Thread(target=the_thread, args=(window,), daemon=True).start()
|
||||
if event == THREAD_EVENT:
|
||||
cp(f'Data from the thread ', colors='white on purple', end='')
|
||||
cp(f'{values[THREAD_EVENT]}', colors='white on red')
|
||||
window.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
These 2 particular demos have a LOT of comments showing you where to add your code, etc.. The amount of code to do this is actually quite small and you don't need to understand the mechanisms used if you simply follow the demo that's been prepared for you.
|
||||
|
||||
### Multithreaded Programs
|
||||
|
||||
|
@ -1129,7 +1210,26 @@ theme_previewer
|
|||
|
||||
The first step is to create the window object using the desired window customizations.
|
||||
|
||||
Note - There is no direct support for "**modal windows**" in PySimpleGUI. All windows are accessible at all times unless you manually change the windows' settings.
|
||||
## Modal Windows (only applied to tkinter port currently
|
||||
)
|
||||
NOTE - as of PySimpleGUI 4.25.0 Modal Windows are supported! By default the `popup` windows that block will be marked Modal by default. This is a somewhat risky change because your expisting applications will behave differently. However, in theory, you shouldn't have been interacting with other windows while the popup is active. All of those actions are at best queued. It's implementation dependent.
|
||||
|
||||
"Modal" in this case means that you must close this "modal" window before you will be able to interact with windows created before this window. Think about an "about" box. You normally have to close this little popup in most programs. So, that's what PySimpleGUI is doing now.
|
||||
|
||||
## Making your window modal
|
||||
|
||||
To make a Modal Wio=ndow you have 2 options.
|
||||
|
||||
1. Set the `moodel=True` parameter in your Window calls.
|
||||
|
||||
2. Call the method `Window.make_modal()` to chance a window from non-modal to modal. There is no modal to non-modal. Don't see the need for one. If one comes up, sure!
|
||||
|
||||
|
||||
### Disabling modal windows
|
||||
|
||||
Popups that block are the only windows that have modal on by default. There is a modal parameter than you can set to False to turn it off.
|
||||
|
||||
For the earlier than 4.25.0 and other ports of PySimpleGUI There is no direct support for "**modal windows**" in PySimpleGUI. All windows are accessible at all times unless you manually change the windows' settings.
|
||||
|
||||
|
||||
**IMPORTANT** - Many of the `Window` methods require you to either call `Window.read` or `Window.Finalize` (or set `finalize=True` in your `Window` call) before you call the method. This is because these 2 calls are what actually creates the window using the underlying GUI Framework. Prior to one of those calls, the methods are likely to crash as they will not yet have their underlying widgets created.
|
||||
|
@ -1773,6 +1873,160 @@ Then to turn off return values for that element, the `Multiline` element would b
|
|||
sg.Multiline(size=(40,8), key='-MLINE-' + sg.WRITE_ONLY_KEY)
|
||||
```
|
||||
|
||||
## Key Errors - Key error recovery algorithm
|
||||
|
||||
In the primary (tkinter) port of PySimpleGUI, starting in version 4.27.0 (not yet on PyPI... but available on GitHub as 4.26.0.14+)
|
||||
|
||||
There are now 3 controls over key error handling and a whole new era of key reporting.
|
||||
|
||||
```python
|
||||
SUPPRESS_ERROR_POPUPS = False
|
||||
SUPPRESS_RAISE_KEY_ERRORS = False
|
||||
SUPPRESS_KEY_GUESSING = False
|
||||
```
|
||||
|
||||
You can modify these values by calling `set_options`.
|
||||
|
||||
```python
|
||||
sg.set_options(suppress_raise_key_errors=False, suppress_error_popups=False, suppress_key_guessing=False)
|
||||
```
|
||||
|
||||
A basic definition of them are:
|
||||
`suppress_error_popups` - Disables error popups that are generated within PySimpleGUI itself to not be shown
|
||||
`suppress_raise_key_errors` - Disables raising a key error if a key or a close match are not found
|
||||
`suppress_key_guessing` - Disables the key guessing algorithm should you have a key error
|
||||
|
||||
With the defaults left as defined (all `False`), here is how key errors work.
|
||||
|
||||
This is the program being used in this example:
|
||||
|
||||
```python
|
||||
import PySimpleGUI as sg
|
||||
|
||||
def main():
|
||||
sg.set_options(suppress_raise_key_errors=False, suppress_error_popups=False, suppress_key_guessing=False)
|
||||
|
||||
layout = [ [sg.Text('My Window')],
|
||||
[sg.Input(k='-IN-'), sg.Text(size=(12,1), key='-OUT-')],
|
||||
[sg.Button('Go'), sg.Button('Exit')] ]
|
||||
|
||||
window = sg.Window('Window Title', layout, finalize=True)
|
||||
|
||||
while True: # Event Loop
|
||||
event, values = window.read()
|
||||
print(event, values)
|
||||
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||
break
|
||||
window['-O U T'].update(values['-IN-'])
|
||||
window.close()
|
||||
|
||||
|
||||
def func():
|
||||
|
||||
main()
|
||||
|
||||
func()
|
||||
```
|
||||
|
||||
A few things to note about it:
|
||||
|
||||
* There are multiple levels of functions being called, not just a flat program
|
||||
* There are 2 keys explicitly defined, both are text at this point (we'll change them later)
|
||||
* There are 2 lookups happening, one with `window` the other with `values`
|
||||
|
||||
This key error recovery algorithm only applies to element keys being used to lookup keys inside of windows. The `values` key lookup is a plain dictionary and so nothing fancy is done for that lookup.
|
||||
|
||||
|
||||
### Example 1 - Simple text string misspelling
|
||||
|
||||
In our example, this line of code has an error:
|
||||
|
||||
```python
|
||||
window['-O U T'].update(values['-IN-'])
|
||||
```
|
||||
|
||||
There are multiple problems with the key `'-OUT-'`. It is missing a dash and it has a bunch of extra spaces.
|
||||
|
||||
When the program runs, you'll first see the layout with no apparent problems:
|
||||
|
||||

|
||||
|
||||
|
||||
Clicking the OK button will cause the program to return from `window.read()` and thus hit our bad key. The result will be a popup window that resembles this:
|
||||
|
||||

|
||||
|
||||
|
||||
Note a few things about this error popup. Your shown your bad key and you're also shown what you likely meant. Additionally, you're shown the filename, the line number and the line of code itself that has the error.
|
||||
|
||||
Because this error was recoverable, the program continues to run after you close the error popup. The result is what you expect from this program... the output field is the same as your information input.
|
||||
|
||||

|
||||
|
||||
|
||||
### Example 2 - Tuple error
|
||||
|
||||
Keys can be a variety of types, including tuples. In this particular program we have a tuple specified in the layout and have used an incorrect tuple in the lookup. Once again the recovery process worked and the program continued.
|
||||
|
||||

|
||||
|
||||
|
||||
### Example 3 - No close match found
|
||||
|
||||
In this example, as you can see in the error popup, there was such a mismatch that no substitution could be performed.
|
||||
|
||||

|
||||
|
||||
|
||||
This is an unrecoverable error, so a key error exception is raised.
|
||||
|
||||
```python
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 25, in <module>
|
||||
func()
|
||||
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 23, in func
|
||||
main()
|
||||
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 17, in main
|
||||
window[(1,2,3)].update(values['-IN-'])
|
||||
File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 8591, in __getitem__
|
||||
return self.FindElement(key)
|
||||
File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 7709, in FindElement
|
||||
raise KeyError(key)
|
||||
KeyError: (1, 2, 3)
|
||||
```
|
||||
|
||||
If you're running an IDE such as PyCharm, you can use the information from the assert to jump to the line of code in your IDE based on the crash data provided.
|
||||
|
||||
|
||||
### Choose Your Desired Combination
|
||||
|
||||
There are enough controls on this error handling that you can control how you want your program to fail. If you don't want any popups, and no guessing and would instead like to simply get an exception when the key error happens, then call `set_options` with this combination:
|
||||
|
||||
|
||||
```python
|
||||
sg.set_options(suppress_raise_key_errors=False, suppress_error_popups=True, suppress_key_guessing=True)
|
||||
```
|
||||
|
||||
This will cause Example #1 above to immediately get an exception when hitting the statement with the error. Even though the guessing is turned off, you're still provided with the closest match to help with your debugging....
|
||||
|
||||
```
|
||||
** Error looking up your element using the key: -O U T The closest matching key: -OUT-
|
||||
Traceback (most recent call last):
|
||||
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 25, in <module>
|
||||
func()
|
||||
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 23, in func
|
||||
main()
|
||||
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 17, in main
|
||||
window['-O U T'].update(values['-IN-'])
|
||||
File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 8591, in __getitem__
|
||||
return self.FindElement(key)
|
||||
File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 7709, in FindElement
|
||||
raise KeyError(key)
|
||||
KeyError: '-O U T'
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Common Element Parameters
|
||||
|
@ -2660,7 +2914,6 @@ For example, this layout has a single slider element that spans several rows fol
|
|||
|
||||
Without a Column Element you can't create a layout like this. But with it, you should be able to closely match any layout created using tkinter only.
|
||||
|
||||
|
||||
```python
|
||||
|
||||
import PySimpleGUI as sg
|
||||
|
@ -2695,9 +2948,10 @@ sg.Popup(event, values, line_width=200)
|
|||
|
||||
```
|
||||
|
||||
### Column, Frame, Tab, Window element_justification
|
||||
|
||||
Beginning in Release 4.3 you can set the justification for any container element. This is done through the `element_justification` parameter. This will greatly help anyone that wants to center all of their content in a window. Previously it was difficult to do these kinds of layouts, if not impossible.
|
||||
## Columns As a Way to Modify Elements
|
||||
|
||||
Sometimes Columns are used to contain a single elemnet, but to give that elemously it was difficult to do these kinds of layouts, if not impossible.
|
||||
|
||||
justify the `Column` element's row by setting the `Column`'s `justification` parameter.
|
||||
|
||||
|
@ -3675,6 +3929,47 @@ You'll find the pattern - `window.Element(key)` in older code. All of code afte
|
|||
|
||||
Note that to change a progress meter's progress, you call `update_bar`, not `update`. A change to this is being considered for a future release.
|
||||
|
||||
# Cursors - Setting for Elements and Windows
|
||||
|
||||
It is possible to change the normal arrow cursor into something else by setting the cursor for an element or the entire window. The result will be the cursor changing when you move the mouse over the elements or Window.
|
||||
|
||||
One of the best examples is URLs. Users are accustomed to seeing a hand cursor when the mouse is moved over a link. By setting the cursor to a hand for a Text element that has text that is in the format of a URL, it signals to the user that it's a link that can be clicked.
|
||||
|
||||
The `set_cursor` method is used to set the cursor for an element. Perform an element look-up or use a variable containing an element, and call the `set_cursor` method, passing in a string that selects the cursor. The valid cursor names are documented in the tkinter docs as this call maps directly to a tkinter call.
|
||||
|
||||
These cursor strings were obtained from the Tk manual and are what you pass into the `set_cursor` methods.
|
||||
|
||||
## Windows Level Cursor
|
||||
|
||||
You can also set the cursor for the Window as a whole, including the margins and areas elements don't directly fill. Call `Window.set_cursor()` to set the cursor at the Window level.
|
||||
|
||||
## Valid Cursor Strings
|
||||
|
||||
`X_cursor, arrow, based_arrow_down, based_arrow_up, boat, bogosity, bottom_left_corner, bottom_right_corner, bottom_side, bottom_tee, box_spiral, center_ptr, circle, clock, coffee_mug, cross, cross_reverse, crosshair, diamond_cross, dot, dotbox, double_arrow, draft_large, draft_small, draped_box, exchange, fleur, gobbler, gumby, hand1, hand2, heart, icon, iron_cross, left_ptr, left_side, left_tee, leftbutton, ll_angle, lr_angle, man, middlebutton, mouse, pencil, pirate, plus, question_arrow, right_ptr, right_side, right_tee, rightbutton, rtl_logo, sailboat, sb_down_arrow, sb_h_double_arrow, sb_left_arrow, sb_right_arrow, sb_up_arrow, sb_v_double_arrow, shuttle, sizing, spider, spraycan, star, target, tcross, top_left_arrow, top_left_corner, top_right_corner, top_side, top_tee, trek, ul_angle, umbrella, ur_angle, watch, xterm`
|
||||
|
||||
|
||||
## No Cursor
|
||||
|
||||
To specify no cursor should be shown, the cursor `'no'` can be used on some platforms
|
||||
|
||||
## Windows OS Specific
|
||||
|
||||
One windows, these cursors map to native Windows cursors:
|
||||
|
||||
`arrow, center_ptr, crosshair, fleur, ibeam, icon, sb_h_double_arrow, sb_v_double_arrow, watch, xterm`
|
||||
|
||||
And these are also available:
|
||||
|
||||
`no, starting, size, size_ne_sw, size_ns, size_nw_se, size_we, uparrow, wait`
|
||||
|
||||
## Mac OS Specific
|
||||
|
||||
`arrow, cross, crosshair, ibeam, plus, watch, xterm`
|
||||
|
||||
And these additional native cursors are available for the Mac
|
||||
|
||||
`copyarrow, aliasarrow, contextualmenuarrow, text, cross-hair, closedhand, openhand, pointinghand, resizeleft, resizeright, resizeleftright, resizeup, resizedown, resizeupdown, none, notallowed, poof, countinguphand, countingdownhand, countingupanddownhand, spinning`
|
||||
|
||||
|
||||
# Keyboard & Mouse Capture
|
||||
|
||||
|
@ -4203,6 +4498,400 @@ Exception module 'tkinter' has no attribute '__version__'
|
|||
```
|
||||
---
|
||||
|
||||
# User Settings API
|
||||
|
||||
In release 4.30.0 there is a new set of API calls available to help with "user settings". Think of user settings as a dictionary that is automatically written to your hard drive. That's basically what it is. Underpinning the code is the JSON package provided by Python.
|
||||
|
||||
While using JSON files to save and load a settings dictionary isn't very difficult, it is still code you'll need to write if you want to save settings as part of your GUI. Since having "settings" for a GUI based program isn't uncommon, it made sense to build this capability into PySimpleGUI. Clearly you can still use your own method for saving settings, but if you're looking for a simple and easy way to do it, these calls are likely about as easy as it gets.
|
||||
|
||||
There have already been some demo programs written that use JSON files to store settings. You can expect that this capability will begin to show up in more demos in the future since it's now part of PySimpleGUI.
|
||||
|
||||
User settings are stored in a Python dictionary which is saved to / loaded from disk. Individual settings are thus keys into a dictionary. You do not need to explicitly read nor write the file. Changing any entry will cause the file to be saved. Reading any entry will cause the file to be read if it hasn't already been read.
|
||||
|
||||
## Two Interfaces
|
||||
|
||||
There are 2 ways to access User Settings
|
||||
|
||||
1. User Settings function calls
|
||||
2. The `UserSettings` class
|
||||
|
||||
They both offer the same basic operations. The class interface has an added benefit of being able to access the individual settings using the same syntax as Python dictionary.
|
||||
|
||||
|
||||
## List of Calls for Function Interface
|
||||
|
||||
|Function|Description|
|
||||
| --- | --- |
|
||||
|user_settings|Returns settings as a dictionary|
|
||||
|user_settings_delete_entry|Deletes a setting|
|
||||
|user_settings_delete_filename|Deletes the settings file|
|
||||
|user_settings_file_exists|Returns True if settings file specified exists|
|
||||
|user_settings_filename|Returns full path and filename of current settings file|
|
||||
|user_settings_get_entry|Returns value for a setting. If no setting found, then specified default value is returned|
|
||||
|user_settings_load|Loads dictionary from the settings file. This is not normally needed||
|
||||
|user_settings_save|Saves settings to current or newly specified file. Not normally needed|
|
||||
|user_settings_set_entry|Sets an entry to a particular value
|
||||
|user_settings_write_new_dictionary|Writes a specified dictionary to settings file|
|
||||
|
||||
|
||||
## Operations
|
||||
|
||||
There are 2 categories that the calls can be divided into.
|
||||
|
||||
1. File operations
|
||||
2. Settings operations
|
||||
|
||||
File operations involve working with the JSON file itself. They include:
|
||||
* Setting the path and/or filename
|
||||
* Load/save the file (these are somewhat optional as the saving loading/saving is done automatically)
|
||||
* Deleting the settings file
|
||||
* Checking if settings file exists
|
||||
|
||||
Generally speaking, a setting is specified with a key which is generally a string. Settings operations are for working with the individual settings and include:
|
||||
* Get the value of a setting (returns a default value if not found)
|
||||
* Set the value of a setting (also saves the settings to disk)
|
||||
|
||||
Any setting operation may cause the file to be written. This is because a "get" operation can include returning a default value if the setting isn't found. This means a new entry is made in your settings dictionary is one didn't exist before. Since a new entry is made, that means it needs to be also be written to disk.
|
||||
|
||||
## Filenames
|
||||
|
||||
The settings filename defaults the filename of your Python file making the call with the extension ".json" added. If your Python program is called `test.py` then your default settings filename will be `test.json`.
|
||||
|
||||
In addition to the filename having a default value, the path to the file also has a default value. The default depends on your operating system.
|
||||
|
||||
|Operating System|Default Path|
|
||||
| --- | --- |
|
||||
| Windows | \user\user_name\AppData\Local\PySimpleGUI\settings |
|
||||
| Linux | ~/.config/PySimpleGUI/settings |
|
||||
| Mac | ~/Library/Application Support/PySimpleGUI/settings |
|
||||
|
||||
When calling the User Settings APIs, if a parameter is named `filename`, you can specify a full path or just the filename. This will save you the trouble of having to split up your path and filename in your code. If you specify only the path, the the filename will be added to that path and named as defined earlier.
|
||||
|
||||
Like the rest of PySimpleGUI, the idea is for you to write as little code as possible. The default values for the filename and path should be fine for you to use. They will be stored in a location on your system that is meant to store user settings.
|
||||
|
||||
|
||||
### Setting Filename
|
||||
|
||||
If you want to see what the current filename is for your settings, then you can call `user_settings_filename()` with no parameters and you'll get back an absolute path and filename.
|
||||
|
||||
To make the code for specifying the folder and filename as simple as possible, the 2 parts are separated in the call specifying the name of the settings file. However, it is possible to supply a full and complete folder + filename as well.
|
||||
|
||||
The default filename for your settings file is the name of the file that makes the call to the User Settings API's with the `.py` extension changed to a `.json` extension. If your source file is called `demo.py`, then your settings filename will be `demo.json`.
|
||||
|
||||
#### Setting only the filename
|
||||
|
||||
If you want to control the name of the file and/or the path to the settings file, then you will use the `user_settings_filename` call. This function takes 2 parameters.
|
||||
|
||||
```python
|
||||
user_settings_filename(filename=None, path=None)
|
||||
```
|
||||
|
||||
If you set only the path, then the filename will default to the value already described. If you set only the filename, then the path will be the default path is dependent on your operating system. See the table above for the locations for each OS.
|
||||
|
||||
```python
|
||||
import PySimpleGUI as sg
|
||||
|
||||
sg.user_settings_filename(filename='my_settings.json')
|
||||
print(sg.user_settings_filename())
|
||||
```
|
||||
|
||||
If you are running on Windows, then the result of running this code will be this printed on the console:
|
||||
|
||||
```
|
||||
C:\Users\your_use_name\AppData\Local\PySimpleGUI\settings\my_settings.json
|
||||
```
|
||||
|
||||
You are not restricted to naming your settings file to an extension of .json. That is simply the default extension that's used by PySimpleGUI. You can use any extension you would like, including no extension.
|
||||
|
||||
#### Setting only the path
|
||||
|
||||
Maybe you don't care about the settings filename itself, but you do care about where the settings are stored. Let's say you want the settings to be stored in the same folder as your Python source file. Specifying `path='.'` will achieve this.
|
||||
|
||||
|
||||
#### Setting a fully qualified filename
|
||||
|
||||
If you want to specify the full absolute path and filename of the settings file, you can do it by using the filename parameter. Instead of passing the filename only, pass in a fully qualified path and filename. If you want to name your settings file `a:\temp\my_settings`, then your call will look like this:
|
||||
|
||||
|
||||
```python
|
||||
sg.user_settings_filename(filename=r'a:\temp\my_settings')
|
||||
```
|
||||
|
||||
You are not required to break your file down into 2 parameters. You could if you wanted to however. The equivalent to the above call using 2 parameters would be:
|
||||
|
||||
```python
|
||||
sg.user_settings_filename(filename='my_settings' , path=r'a:\temp')
|
||||
```
|
||||
|
||||
|
||||
### Getting the current filename
|
||||
|
||||
Calling `user_settings_filename` with no parameters will return the full path and filename of your settings file as a single string.
|
||||
|
||||
|
||||
### File Loading / Saving
|
||||
|
||||
Generally speaking you will not need to load or save your settings file. It is automatically saved after every change.
|
||||
|
||||
Note that reading a setting can also cause the file to be written. If you read a setting and the setting did not exist, then your call to `user_settings_get_entry` will return the default value you specified. As a result, the dictionary is updated with this default value and in return the file is written with this value as well.
|
||||
|
||||
One of the situations where you may want to explicitly read/load the settings file is if you're expecting it to be modified by another program.
|
||||
|
||||
Like so much of PySimpleGUI, as much as possible is automatically done on your behalf. This includes the requirement of saving and loading your settings file. Even naming your settings file is optional.
|
||||
|
||||
## The `UserSettings` Class Interface
|
||||
|
||||
The `UserSettings` class makes working with settings look like a Python dictionary. The familiar [ ] syntax is used to read, write and delete entries.
|
||||
|
||||
### Creating a `UserSettings` Object
|
||||
|
||||
The first step is to create your setting object. The parameters are the same as calling the `user_settings_filename` function. If you want to use the default values, then leave the parameters unchanged.
|
||||
|
||||
```python
|
||||
settings = sg.UserSettings()
|
||||
```
|
||||
|
||||
This is the same as calling `sg.user_settings_filename()`
|
||||
|
||||
|
||||
### Reading, Writing, and Deleting an Individual Settings Using [ ] Syntax
|
||||
|
||||
The first operation will be to create the User Settings object.
|
||||
|
||||
```python
|
||||
settings = sg.UserSettings()
|
||||
```
|
||||
|
||||
To read a setting the dictionary-style [ ] syntax is used. If the item's name is `'-item-'`, then reading the value is achieved by writing
|
||||
|
||||
```python
|
||||
item_value = settings['-item-']
|
||||
```
|
||||
|
||||
Writing the setting is the same syntax except the expression is reversed.
|
||||
|
||||
```python
|
||||
settings['-item-'] = new_value
|
||||
```
|
||||
|
||||
To delete an item, again the dictionary style syntax is used.
|
||||
|
||||
```python
|
||||
del settings['-item-']
|
||||
```
|
||||
|
||||
You can also call the delete_entry method to delete the entry.
|
||||
|
||||
```python
|
||||
settings.delete_entry('-item-')
|
||||
```
|
||||
|
||||
### `UserSettings` Methods
|
||||
|
||||
You'll find all of the `UserSettings` methods available to you detailed in the Call Reference documentation.
|
||||
|
||||
One operation in particular that is not achievable using the [ ] notation is a "get" operation with a default value. For dictionaries, this method is `get` and for the `UserSettings` class the method is also called `get`. They both have an optional second parameter that represents a "default value" should the key not be found in the dictionary.
|
||||
|
||||
If you would like a setting with key `'-item-'` to return an empty string `''` instead of `None` if they key isn't found, then you can use this code to achieve that:
|
||||
|
||||
```python
|
||||
value = settings.get('-item-', '')
|
||||
```
|
||||
|
||||
It's the same kind of syntax that you're used to using with dictionaries.
|
||||
|
||||
|
||||
### Default Value
|
||||
|
||||
Normally the default value will be `None` if a key is not found and you get the value of the entry using the bracket format:
|
||||
|
||||
```python
|
||||
item_value = settings['-item-']
|
||||
```
|
||||
|
||||
You can change the default value by calling `settings.set_default_value(new_default)`. This will set the default value to return in the case when no key is found. Note that an exception is not raised when there is a key error (see next section on error handling). Instead, the default value is returned with a warning displayed.
|
||||
|
||||
|
||||
## Displaying the Settings Dictionary
|
||||
|
||||
The class interface makes it easy to dump out the dictionary. If you print the UserSettings object you'll get a printout of the dictionary.
|
||||
|
||||
Note that you'll need to "load" the settings from disk if you haven't performed any operations on the settings.
|
||||
|
||||
```python
|
||||
settings = sg.UserSettings()
|
||||
settings.load()
|
||||
print(settings)
|
||||
```
|
||||
|
||||
If you were to print the dictionary after creating the object, then the `load` is not needed
|
||||
|
||||
```python
|
||||
settings = sg.UserSettings()
|
||||
print(settings['-item-'])
|
||||
print(settings)
|
||||
```
|
||||
|
||||
To print the dictionary using the function call interface:
|
||||
|
||||
```python
|
||||
print(sg.user_settings())
|
||||
```
|
||||
|
||||
## Error Handling for User Settings
|
||||
|
||||
From a GUI perspective, user settings are not critical to the GUI operations itself. There is nothing about settings that will cause your window to not function. As a result, errors that occur in the User Settings are "soft errors". An error message is displayed along with information about how you called the function, when possible, and then execution continues.
|
||||
|
||||
One reason for treating these as soft errors and thus not raising an exception is that raising an exception will crash your GUI. If you have redirected your output, which many GUIs do, then you will see no error information and your window will simply disappear. If you double clicked a .py file to launch your GUI, both the GUI and the console window will instantly disappear if the GUI crashes, leaving you no information to help you debug the problem.
|
||||
|
||||
The only time errors can occur are during file operations. Typically these errors happen because you've specified a bad path or you don't have write permission for the path you specified.
|
||||
|
||||
Example error message. If you executed this code:
|
||||
|
||||
```python
|
||||
def main():
|
||||
sg.user_settings_filename(path='...')
|
||||
sg.user_settings_set_entry('-test-',123)
|
||||
```
|
||||
|
||||
Then you'll get an error when trying to set the '-test-' entry because `'...'` is not a valid path.
|
||||
|
||||
```
|
||||
*** Error saving settings to file:***
|
||||
...\scratch_1065.json [Errno 2] No such file or directory: '...\\scratch_1065.json'
|
||||
The PySimpleGUI internal reporting function is save
|
||||
The error originated from:
|
||||
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_1065.py"
|
||||
line 8
|
||||
in main
|
||||
sg.user_settings_set_entry('-test-',123)
|
||||
```
|
||||
|
||||
You should be able to easily figure out these errors as they are file operations and the error messages are clear in detailing what's happened and where the call originated.
|
||||
|
||||
### Silenting the Errors
|
||||
|
||||
If you're the type that doesn't want to see any error messages printed out on your console, then you can silence the error output.
|
||||
|
||||
When using the class interface, there is a parameter `silent_on_error` that you can set to `True`.
|
||||
|
||||
For the function interface, call the function `user_settings_silent_on_error()` and set the parameter to `True`
|
||||
|
||||
|
||||
## Coding Convention for User Settings Keys
|
||||
|
||||
The User Settings prompted a new coding convention that's been added to PySimpleGUI examples. As you're likely aware, keys in layouts have the format `'-KEY-`'. For UserSettings, a similar format is used, but instead of the string being in all upper case, the characters are lower case. In the example below, the user setting for "filename" has a User Setting key of `'-filename-'`. Coding conventions are a good thing to have in your projects. You don't have to follow this one of course, but you're urged to create your own for places in your code that it makes sense. You could say that PEP8 is one giant coding convention for the Python language as a whole. You don't have to follow it, but most Python programmers do. We follow it "by convention".
|
||||
|
||||
The reason this is done in PySimpleGUI is so that the keys are immediately recognizable. Perhaps your application has dictionaries that you use. If you follow the PySimpleGUI coding convention of Element keys have the format `'-KEY-'` and User Settings keys have the format of `'-key-'`, then you'll immediately understand what a specific key is used for. Your company may have its own coding conventions so follow those if appropriate instead of what you see in the PySimpleGUI examples.
|
||||
|
||||
## Example User Settings Usage
|
||||
|
||||
One of the primary places settings are likely to be used is for filenames / folder names. How many times have you run the same program and needed to enter the same filename? Even if the name of the file is on your clipboard, it's still a pain in the ass to paste it into the input field every time you run the code. Wouldn't it be so much simpler if your program remembered the last value you entered? Well, that's exactly why this set of APIs was developed.... again it was from laziness that this capability gained life.
|
||||
|
||||
If you want your `Input` elements to default to an entry from your settings, then you simply set the first parameter (`default_text`) to the value of a setting from your settings file.
|
||||
|
||||
Let's say your layout had this typical file input row:
|
||||
|
||||
```python
|
||||
[sg.Input(key='-IN-'), sg.FileBrowse()]
|
||||
```
|
||||
|
||||
To automatically fill in the `Input` to be the last value entered, use this layout row:
|
||||
|
||||
```python
|
||||
[sg.Input(sg.user_settings_get_entry('-filename-', ''), key='-IN-'), sg.FileBrowse()]
|
||||
```
|
||||
|
||||
When your user clicks OK or closes the window in a way that is in a positive way (instead of cancelling), then add this statement to save the value.
|
||||
|
||||
```python
|
||||
sg.user_settings_set_entry('-filename-', values['-IN-'])
|
||||
```
|
||||
|
||||
Here's an entire program demonstrating this way of using user settings
|
||||
|
||||

|
||||
|
||||
```python
|
||||
import PySimpleGUI as sg
|
||||
|
||||
layout = [[sg.Text('Enter a filename:')],
|
||||
[sg.Input(sg.user_settings_get_entry('-filename-', ''), key='-IN-'), sg.FileBrowse()],
|
||||
[sg.B('Save'), sg.B('Exit Without Saving', key='Exit')]]
|
||||
|
||||
window = sg.Window('Filename Example', layout)
|
||||
|
||||
while True:
|
||||
event, values = window.read()
|
||||
if event in (sg.WINDOW_CLOSED, 'Exit'):
|
||||
break
|
||||
elif event == 'Save':
|
||||
sg.user_settings_set_entry('-filename-', values['-IN-'])
|
||||
|
||||
window.close()
|
||||
```
|
||||
|
||||
In 2 lines of code you've just made life for your user so much easier. And, by not specifying a location and name for your file, the settings are stored out of sight / out of mind. If you wanted to have the settings be stored with your program file so that it's more visible, then add this statement before your layout:
|
||||
|
||||
```python
|
||||
sg.user_settings_filename(path='.')
|
||||
```
|
||||
|
||||
## Example Using UserSettings Class with [ ] Syntax
|
||||
|
||||
The same example can be written using the `UserSettings` class and the [ ] lookup syntax.
|
||||
|
||||
Here's the same program as above.
|
||||
|
||||
```python
|
||||
import PySimpleGUI as sg
|
||||
|
||||
settings = sg.UserSettings()
|
||||
|
||||
layout = [[sg.Text('Enter a filename:')],
|
||||
[sg.Input(settings.get('-filename-', ''), key='-IN-'), sg.FileBrowse()],
|
||||
[sg.B('Save'), sg.B('Exit Without Saving', key='Exit')]]
|
||||
|
||||
window = sg.Window('Filename Example', layout)
|
||||
|
||||
while True:
|
||||
event, values = window.read()
|
||||
if event in (sg.WINDOW_CLOSED, 'Exit'):
|
||||
break
|
||||
elif event == 'Save':
|
||||
settings['-filename-'] = values['-IN-']
|
||||
|
||||
window.close()
|
||||
```
|
||||
|
||||
If you were to place these 2 examples in the same file so that one ran after the other, you will find that the same settings file is used and thus the value saved in the first example will be read by the second one.
|
||||
|
||||
There was one additional line of code added:
|
||||
|
||||
```python
|
||||
settings.set_default_value('') # Set the default not-found value to ''
|
||||
|
||||
```
|
||||
|
||||
Strictly speaking, this line isn't needed because the Input Element now takes `None` to be the same as a value of `''`, but to produce identical results I added this line of code.
|
||||
|
||||
|
||||
## Demo Programs
|
||||
|
||||
There are a number of demo programs that show how to use UserSettings to create a richer experience for your users by remember the last value input into input elements or by adding a Combobox with a history of previously entered values. These upgrades make for a much easier to use GUI, especially when you find yourself typing in the same values or using the same files/folders.
|
||||
|
||||
|
||||
## Brief Caution - User Settings Stick Around
|
||||
|
||||
If you're using the default path, remember that previous runs of your file may have old settings that are still in your settings file. It can get confusing when you've forgotten that you previously wrote a setting. Not seeing the filename can have drawbacks like this.
|
||||
|
||||
Also, because the settings automatically save after every update, it can be easy to accidently overwrite a previously saved setting. If you want to avoid this, then perhaps it's best that you work with a dictionary within your code and then explicitly save your dictionary when you're ready to commit it to disk.
|
||||
|
||||
To save your Python dictionary to a settings file, simply call `user_settings_write_new_dictionary(dict)`, passing in your dictionary as the parameter.
|
||||
|
||||
|
||||
-------------------------
|
||||
|
||||
|
||||
# Extending PySimpleGUI
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
| 2.7.0 | July 30, 2018 - realtime buttons, window_location default setting
|
||||
| 2.8.0 | Aug 9, 2018 - New None default option for Checkbox element, text color option for all elements, return values as a dictionary, setting focus, binding return key
|
||||
| 2.9.0 | Aug 16,2018 - Screen flash fix, `do_not_clear` input field option, `autosize_text` defaults to `True` now, return values as ordered dict, removed text target from progress bar, rework of return values and initial return values, removed legacy Form.Refresh() method (replaced by Form.ReadNonBlockingForm()), COLUMN elements!!, colored text defaults
|
||||
| 2.10.0 | Aug 25, 2018 - Keyboard & Mouse features (Return individual keys as if buttons, return mouse scroll-wheel as button, bind return-key to button, control over keyboard focus), SaveAs Button, Update & Get methods for InputText, Update for Listbox, Update & Get for Checkbox, Get for Multiline, Color options for Text Element Update, Progess bar Update can change max value, Update for Button to change text & colors, Update for Image Element, Update for Slider, Form level text justification, Turn off default focus, scroll bar for Listboxes, Images can be from filename or from in-RAM, Update for Image). Fixes - text wrapping in buttons, msg box, removed slider borders entirely and others
|
||||
| 2.11.0 | Aug 29, 2018 - Lots of little changes that are needed for the demo programs to work. Buttons have their own default element size, fix for Mac default button color, padding support for all elements, option to immediately return if list box gets selected, FilesBrowse button, Canvas Element, Frame Element, Slider resolution option, Form.Refresh method, better text wrapping, 'SystemDefault' look and feel settin
|
||||
| 2.20.0 | Sept 4, 2018 - Some sizable features this time around of interest to advanced users. Renaming of the MsgBox functions to Popup. Renaming GetFile, etc, to PopupGetFile. High-level windowing capabilities start with Popup, PopupNoWait/PopupNonblocking, PopupNoButtons, default icon, change_submits option for Listbox/Combobox/Slider/Spin/, New OptionMenu element, updating elements after shown, system defaul color option for progress bars, new button type (Dummy Button) that only closes a window, SCROLLABLE Columns!! (yea, playing in the Big League now), LayoutAndShow function removed, form.Fill - bulk updates to forms, FindElement - find element based on key value (ALL elements have keys now), no longer use grid packing for row elements (a potentially huge change), scrolled text box sizing changed, new look and feel themes (Dark, Dark2, Black, Tan, TanBlue, DarkTanBlue, DarkAmber, DarkBlue, Reds, Green)
|
||||
| 2.10.0 | Aug 25, 2018 - Keyboard & Mouse features (Return individual keys as if buttons, return mouse scroll-wheel as button, bind return-key to button, control over keyboard focus), SaveAs Button, Update & Get methods for InputText, Update for Listbox, Update & Get for Checkbox, Get for Multiline, Color options for Text Element Update, Progress bar Update can change max value, Update for Button to change text & colors, Update for Image Element, Update for Slider, Form level text justification, Turn off default focus, scroll bar for Listboxes, Images can be from filename or from in-RAM, Update for Image). Fixes - text wrapping in buttons, msg box, removed slider borders entirely and others
|
||||
| 2.11.0 | Aug 29, 2018 - Lots of little changes that are needed for the demo programs to work. Buttons have their own default element size, fix for Mac default button color, padding support for all elements, option to immediately return if list box gets selected, FilesBrowse button, Canvas Element, Frame Element, Slider resolution option, Form.Refresh method, better text wrapping, 'SystemDefault' look and feel setting
|
||||
| 2.20.0 | Sept 4, 2018 - Some sizable features this time around of interest to advanced users. Renaming of the MsgBox functions to Popup. Renaming GetFile, etc, to PopupGetFile. High-level windowing capabilities start with Popup, PopupNoWait/PopupNonblocking, PopupNoButtons, default icon, change_submits option for Listbox/Combobox/Slider/Spin/, New OptionMenu element, updating elements after shown, system default color option for progress bars, new button type (Dummy Button) that only closes a window, SCROLLABLE Columns!! (yea, playing in the Big League now), LayoutAndShow function removed, form.Fill - bulk updates to forms, FindElement - find element based on key value (ALL elements have keys now), no longer use grid packing for row elements (a potentially huge change), scrolled text box sizing changed, new look and feel themes (Dark, Dark2, Black, Tan, TanBlue, DarkTanBlue, DarkAmber, DarkBlue, Reds, Green)
|
||||
| 2.30.0 | Sept 6, 2018 - Calendar Chooser (button), borderless windows, load/save form to disk
|
||||
| 3.0.0 | Sept 7, 2018 - The "fix for poor choice of 2.x numbers" release. Color Chooser (button), "grab anywhere" windows are on by default, disable combo boxes, Input Element text justification (last part needed for 'tables'), Image Element changes to support OpenCV?, PopupGetFile and PopupGetFolder have better no_window option
|
||||
| 3.01.01 | Sept 10, 2018 - Menus! (sort of a big deal)
|
||||
|
@ -1270,17 +1270,383 @@ Horizontal Separator, cprint, docstrings
|
|||
* Fix for removing too many PySimpleGUI installs when using the GitHub upgrade tooltip
|
||||
|
||||
|
||||
## 4.22.0 PySimpleGUI 28-Jun-2020
|
||||
|
||||
More cprint stuff
|
||||
|
||||
* Additional window and key parameter to cprint
|
||||
* May seem like a small change, but the results are powerful
|
||||
* Can now easily "print" to anywhere, in color!
|
||||
|
||||
|
||||
## 4.23.0 PySimpleGUI 3-Jul-2020
|
||||
|
||||
Table Colors Fix - workaround for problems with tables and tree colors in Python 3.7.2 to 3.9+
|
||||
Mac crash fixed - tkinter.TclError: expected boolean value but got "" (hopefully)
|
||||
New shortcut "k" parameter for all elements that is the same as "key" - may be experimental / temporary if not well received
|
||||
More error checks
|
||||
popup extensions
|
||||
|
||||
|
||||
### Upcoming
|
||||
* Fix for missing Table and Tree colors created in tk 8.6.9
|
||||
* This is a problem in all versions of Python 3.7.2 - 3.9.0 with no target fix date published
|
||||
* As a result of no fixes in sight, added a fix in PySimpleGUI if the tk version is 8.6.9
|
||||
* New Element creation parameter "k" - exact same thing as "key" but shorter. Helps with complex layouts
|
||||
* New error reporting on all element.update calls - checks to see if element has been fully created
|
||||
* set_options - new option to supress popup_errors coming from PySimpleGUI.py
|
||||
* Mac specific crash fix - if on a Mac, no longer calling wm_overrideredirect as it crashes the Mac now
|
||||
* Additional error checking (shows error instead of asserting:
|
||||
* Check for Widget creation before widget operations like bind, unbind, expand
|
||||
* Check for window finalize / read before some window operations like maximize, hide, etc
|
||||
* docstrings - more added. Fixed up a number of missing / erroneous ones
|
||||
* Tree element - caches images so that will not create new ones if previously used on another Tree item
|
||||
* popup - two new options
|
||||
* any_key_closes - bool. If True, any key pressed will close the window
|
||||
* image - can be a bytes (base64) or string (filename). Image will be shown at top of the popup
|
||||
* all popups - new image parameter (base64 or string)
|
||||
* a few new built-in icons
|
||||
|
||||
|
||||
There will always be overlapping work as the ports will never actually be "complete" as there's always something new that can be built. However there's a definition for the base functionality for PySimpleGUI. This is what is being strived for with the current ports that are underway.
|
||||
## 4.24.0 PySimpleGUI 3-Jul-2020
|
||||
|
||||
The current road ahead is to complete these ports - Qt (very close), Web (pretty close), Wx (not all that close).
|
||||
Selective control over tk 8.6.9 treeview color patch
|
||||
|
||||
PySimpleGUIDroid is in the works....
|
||||
* Disabled the code that patched the problem with background colors for Tree and Table elements
|
||||
* Can enable the patched code by calling set_options
|
||||
* To enable set parameter enable_treeview_869_patch = True (defaults to false)
|
||||
|
||||
In addition to the ports there is ongoing work with educators that want to bring PySimpleGUI into their classrooms. Some projects have already started with teachers. One effort is to examine a number of books that teach Python to kids and convert the exercises to use PySimpleGUI instead of tkinter or command line. Another educational effort is in integrating with Circuit Python. It's unclear exactly how PySimpleGUI will fit into the picture. A board from Adafruit is arriving soon which should help solidify what's possible.
|
||||
|
||||
|
||||
## 4.25.0 PySimpleGUI 17-Jul-2020
|
||||
|
||||
Biggest / most impactful set of changes in a while (fingers crossed)
|
||||
Modal windows
|
||||
Multithreaded Window.write_event_value method
|
||||
stdout re-route to any Multiline
|
||||
table/tree highlights
|
||||
k element parameter
|
||||
|
||||
* New "k" parameter for all elements.
|
||||
* Same as "key"
|
||||
* Created so layouts can be even more compact if desired
|
||||
* New docstring for keys (basically anything except a list)
|
||||
* Popups
|
||||
* New text wrap behavior. Will wrap text between \n in user's string
|
||||
* All popups are now "modal" unless they are non-blocking (can be turned off using new parameter)
|
||||
* New button color and table/tree highlight color format
|
||||
* Colors can still be tuple (text, background)
|
||||
* Can also be a single string with format "text on background" (e.g. "white on red")
|
||||
* Multiline
|
||||
* Automatically refresh window when updating multiline or output elements
|
||||
* For cprint use Multiline's autoscroll setting
|
||||
* New autoscroll parameter in Multiline.print
|
||||
* New parameters to make the print and cprint stuff much easier
|
||||
* write_only=False (so that it's not returned when read)
|
||||
* auto_refresh=False
|
||||
* reroute_stdout=False
|
||||
* reroute_stderr=False
|
||||
* reroute_cprint=False (removes need to call the cprint cprint_set_output_destination function)
|
||||
* Table / Tree Elements
|
||||
* Re-enabled the tk 8.6.9 background color fix again
|
||||
* selected_row_colors=(None, None) - tuple or string
|
||||
* Automatically sets the selected row color based on the theme colors! (uses the button color)
|
||||
* Can use 2 other constants for colors
|
||||
* OLD_TABLE_TREE_SELECTED_ROW_COLORS - ('#FFFFFF', '#4A6984') the old blueish color
|
||||
* ALTERNATE_TABLE_AND_TREE_SELECTED_ROW_COLORS - (SystemHighlightText, SystemHighlight)
|
||||
* Tree image caching happens at the element level now
|
||||
* Window
|
||||
* make_modal - new method to turn a window into a modal window
|
||||
* modal parameter when window is created. Default is False
|
||||
* write_event_value - new method that can be called by threads! This will "queue" an event and a value for the next window.read()
|
||||
* Display an error popup if read a closed window 100+ times (stops you from eating 100% of the CPU time)
|
||||
* was_closed method added - returns True if a window has been closed
|
||||
* Combo - don't select first entry if updated with a new set of values
|
||||
* Tooltip - fix for stuck-on tooltips
|
||||
* New theme_previewer with scrollbars. 3 new parameters
|
||||
* cprint - now has all parameters shown in docstring versus using *args **kwargs
|
||||
* New global variable __tclversion_detailed__ - string with full tkinter version (3 numbers instead of 2)
|
||||
* Warning is displayed if tcl version is found to be 8.5.
|
||||
|
||||
|
||||
## 4.26.0 PySimpleGUI 18-Jul-2020
|
||||
|
||||
* Multi-threaded tkvar initialization location changed so that thread doesn't intialize it now
|
||||
* Removed thread key - no longer needed
|
||||
* Window.write_event_values - now requires both parms
|
||||
* Upgrade button typo
|
||||
|
||||
|
||||
## 4.27.4 PySimpleGUI 3-Aug-2020
|
||||
|
||||
Multi-window support done right!
|
||||
New capabilities for printing, Multiline
|
||||
Main app additions
|
||||
Theme searching
|
||||
|
||||
* read_all_windows - function that reads all currently open windows.
|
||||
* Finally the efficient multi-window solution
|
||||
* No longer need to do round-robin type scheduling
|
||||
* Easily convert existing programs from single to multi-windows
|
||||
* Demo programs with multi-window design patterns all updated
|
||||
* Ideal for "floating palette / toolbar" window adds-ons
|
||||
* Can read with timeout including timeout=0
|
||||
* theme_previewer
|
||||
* search option
|
||||
* button in main app
|
||||
* reset to previous theme following preview
|
||||
* Sponsor button in main app
|
||||
* Theme previewer in main app
|
||||
* Progress bar
|
||||
* colors can use the single string "foreground on background" color format
|
||||
* update_bar combined with update for a single update interface
|
||||
* Better element key error handling
|
||||
* 3 options to control how lookup errors are handled
|
||||
* popup now shows
|
||||
* file, function, line #, actual line of code with error
|
||||
* erroneous key provided
|
||||
* best matching key
|
||||
* will automatically try to continue with best matching key
|
||||
* can assert with key error if desired (true by default)
|
||||
* fix for get item
|
||||
* Up/down arrow bindings for spinner if enabling events
|
||||
* Multiline
|
||||
* new justification parameter on creation and update
|
||||
* print - justification parameter added
|
||||
* cprint - justification parameter added - note tricky to set color of single word but possible
|
||||
* Added mousewheel for Linux return_keyboard_events enabled
|
||||
* Added get_globals function for extending easier
|
||||
* Refactored callbacks
|
||||
* Image element - can clear image by not setting any parameters when calling update
|
||||
* Column Element's Widget member variable now being set
|
||||
* Window's starting window location saved
|
||||
* Early experimental "Move all windows in sync" when using grab_anywhere (coming soon)
|
||||
* Fix for 3.4 (can't use f-strings)
|
||||
|
||||
|
||||
## 4.28.0 PySimpleGUI 3-Aug-2020
|
||||
|
||||
Element pinning for invisibility!
|
||||
|
||||
* Better visible/invisible handling
|
||||
* pin - new function to place an element in a layout that will hold its position
|
||||
* border_width added to Canvas and Graph (so that they will default to 0)
|
||||
* Combobox
|
||||
* button color will match theme's button color
|
||||
* background color set correctly when readonly indicated
|
||||
* Spin element
|
||||
* spin button color set to background color of spinner
|
||||
* spin arrow color automatically set to text color
|
||||
* Bad element key popup - fix for displaying correct line info in some situations
|
||||
|
||||
|
||||
## 4.29.0 PySimpleGUI 25-Aug-2020
|
||||
|
||||
Custom titlebar capabilities (several new features required)
|
||||
Better Alignment
|
||||
Calendar button works again
|
||||
|
||||
* Window.visiblity_changed now refreshes the window
|
||||
* Added Column.contents_changed which will update the scrollbar so corrently match the contents
|
||||
* Separators expand only in 1 direction now
|
||||
* Added 8 SYMBOLS:
|
||||
SYMBOL_SQUARE = '█'
|
||||
SYMBOL_CIRCLE = '⚫'
|
||||
SYMBOL_CIRCLE_OUTLINE = '◯'
|
||||
SYMBOL_UP = '▲'
|
||||
SYMBOL_RIGHT = '►'
|
||||
SYMBOL_LEFT = '◄'
|
||||
SYMBOL_DOWN = '▼'
|
||||
SYMBOL_X = '❎'
|
||||
* New dark themes - dark grey 8, dark grey 9, dark green 9, dark purple 7
|
||||
* When closing window no longer deletes the tkroot variable and rows but instead set to None
|
||||
* Changd no-titlebar code to use try/except. Previously removed for Mac due to tk 8.6.10 errors calling wm_overrideredirect
|
||||
* Fix for Column/window element justification
|
||||
* New vertical_alignment parm for Column, Frame, pin
|
||||
* New layout helper functions - vtop/vcenter/vbottom - Can pass an element or a row of elements
|
||||
* Fixed statusbar expansion
|
||||
* Added disabled button to theme previewer
|
||||
* Fixed grab anywhere stop motion bug - was setting position to None and causing error changed to event.x
|
||||
* Expanded main to include popup tests, theme tests, ability to hide tabs
|
||||
* Grab parameter for Text Element, Column Element
|
||||
* Added tclversion_detailed to get the detailed tkinter version
|
||||
* All themes changed the progress bar definition that had a "DEFAULT" indicator. New constant DEFAULT_PROGRESS_BAR_COMPUTE indicates the other theme colors should be used to create the progess bar colors.
|
||||
* Added expand_x and expand_y parameters to Columns
|
||||
* Fix for Calendar Button. Still needs to be fixed for read_all_windows
|
||||
* Force focus when no-titlebar window. Needed for Raspberry Pi
|
||||
* Added Window.force_focus
|
||||
* No longer closes the hidden master window. Closing it caused a memory leak within tkinter
|
||||
* Disable close on one_line_progress_meter. There is a cancel button that will close the window
|
||||
* Changed back toplevel to no parent - was causing problems with timeout=0 windows
|
||||
|
||||
|
||||
## 4.30.0 PySimpleGUI 14-Oct-2020
|
||||
|
||||
User Settings APIs, lots more themes, theme swatch previewer, test harness additions
|
||||
|
||||
* Added shrink parameter to pin,
|
||||
* added variable Window.maximized,
|
||||
* added main_sdk_help_window function,
|
||||
* New themes - DarkGrey10,DarkGrey11 DarkGrey12 DarkGrey13 DarkGrey14, Python, DarkBrown7
|
||||
* Highlight Thickness for Button, Radio, Input elements
|
||||
* Set to 1 now instead of 0 so that focus can be seen
|
||||
* Color is automatically set for buttons, checkboxes, radio buttons
|
||||
* Color can be manually set for Buttons using `highlight_colors` parameter
|
||||
* Only used by Linux
|
||||
* user_settings APIs
|
||||
* Whole new set of API calls for handling "user settings"
|
||||
* Settings are saved to json file
|
||||
* For more info, see the documentation
|
||||
* Radio.update - added text, background & text colors parameters
|
||||
* Multiline & Output Elements:
|
||||
* added parameter echo_stdout_stderr
|
||||
* if True then stdout & stderr will go to the console AND to the Multiline
|
||||
* "ver" is shortened version string
|
||||
* modal docstring fix in some popups
|
||||
* image parameter implemented in popup_scrolled
|
||||
* Graph.draw_image - removed color, font, angle parameters
|
||||
* fixed blank entry with main program's theme previewer
|
||||
* added Window.set_min_size
|
||||
* error message function for soft errors
|
||||
* focus indicator for Button Checkbox Radio using highlights
|
||||
* added main_sdk_help Window
|
||||
* added theme_previewer_swatches function
|
||||
* added "Buy Me A Coffee" button
|
||||
* updated `pin` layout helper function - added `shrink` parameter
|
||||
* Main debugger window set to keep on top
|
||||
|
||||
|
||||
|
||||
## 4.31.0 PySimpleGUI 13-Nov-2020
|
||||
|
||||
User Settings class, write_event_value fixes, Menus get colors, Mac no_titlebar patch
|
||||
|
||||
* InputText element - Now treating None as '' for default
|
||||
* Combo - handling update calls with both disabled and readonly set
|
||||
* Spin - readonly
|
||||
* Added parameter added when creating
|
||||
* Added parameter to update
|
||||
* Spin.get() now returns value rather than string version of value
|
||||
* Multiline print now autoscrolls by default
|
||||
* FileSaveAs and SaveAs now has default_extension parameter like the popup_get_file has
|
||||
* Button Menu - Color and font changes
|
||||
* New create parameters - background color, text color, disabled text color, item font
|
||||
* Fixed problem with button always being flat
|
||||
* Menu (Menubar) - Color changes
|
||||
* New create paramters - text color, disabled text color.
|
||||
* Hooked up background color parameter that was already there but not functional
|
||||
* write_event_value - fixed race conditions
|
||||
* Window.read() and read_all_windows() now checks the thread queue for events before starting tkinter's mainloop in case events are queued
|
||||
* Window.set_cursor added so that window's cursor can be set just like can be set for individual elements
|
||||
* Icon is now set when no_window option used on popup_get_file or popup_get_folder
|
||||
* Reformatted the theme definitions to save a LOT of lines of code
|
||||
* UserSettings class
|
||||
* Added a class interface for User Settings
|
||||
* Can still use the function interface if desired
|
||||
* One advantage of class is that [ ] can be used to get and set entries
|
||||
* Looks and acts much like a "persistent global dictionary"
|
||||
* The User Settings function interfaces now use the class
|
||||
* main_get_debug_data()
|
||||
* Function added that will display a popup and add to the clipboard data needed for GitHub Issues
|
||||
* Added button to Test Harness to display the popup with version data
|
||||
* Mac - Added parm enable_mac_notitlebar_patch to set_options to enable apply a "patch" if the Window has no_titlebar set.
|
||||
|
||||
|
||||
## 4.32.0 PySimpleGUI 17-Nov-2020
|
||||
|
||||
Menu colors and font, fixes
|
||||
|
||||
* Menu, ButtonMenu, and right click menu now default to theme colors and Window font
|
||||
* The background color for menus is the InputText background color
|
||||
* The text color for menus is the InputText text color
|
||||
* The font defaults to the Window font
|
||||
* These theme colors have worked well in the past as they are the settings used for Table and Tree headers
|
||||
* All settings can be changed
|
||||
* Added ability to set the right click menu colors and font
|
||||
* New parameters added to Window to control right click look
|
||||
* Fixed problem with Button.update.
|
||||
* Was crashing if button color changed to COLOR_SYSTEM_DEFAULT
|
||||
* Fixed problem with right click menus introduced in the previous release
|
||||
* Auto-close windows can now be finalized (previously could not do this)
|
||||
* Window.read with timeout is faster
|
||||
|
||||
|
||||
|
||||
## 4.32.1 PySimpleGUI 17-Nov-2020
|
||||
|
||||
* Bug in finalize code
|
||||
|
||||
|
||||
## 4.33.0 PySimpleGUI 2-Jan-2021 (Happy New Year!)
|
||||
|
||||
The let's kickoff 2021 release!
|
||||
|
||||
Custom Titlebars, Fix for Docstrings so PyCharm 2020 works correctly, New shortcuts, Window Close Attempted
|
||||
|
||||
* Custom Titlebar - new element
|
||||
* Initial reason was Trinket, but great feature overall
|
||||
* Allows windows to be styled to match the colors of the window
|
||||
* Automatically uses theme for colors
|
||||
* Automatically used when running on Trinket
|
||||
* Can specify using them by using set_options, a Titlebar element, or parameters in Window creation
|
||||
* Documentation is coming soonish
|
||||
* Demo exists showing how to use (it's enough that you won't need a Cookbook / detailed docs to add it to your own program)
|
||||
* Changes include adding a 16x16 pixel version of the PySimpleGUI icon
|
||||
* popups - If custom titlebar is set using set_options (and is thus globally applied) then popups will use the custom titlebar
|
||||
* MASSIVE number of changes to docstrings so that PyCharm again works correctly. All Unions had to be changed to use "|" instead
|
||||
* Internal functions added to check what OS is being used rather than having os.platform checks all over thee place
|
||||
* Element.Visible removed. Element.visible property returns current visibility state for element
|
||||
* This is a read-only property
|
||||
* Added dummy Element.update method so that PyCharm doesn't complain about a missing method
|
||||
* InputElement class changed to Input. Both will work as InputElement is now an alias
|
||||
* Fix in Spin.update. Was using an illegal state value of "enable" rather than "normal"
|
||||
* New Shortcuts for Elements
|
||||
* Sp = Spin
|
||||
* SBar = StatusBar
|
||||
* BM = ButtonMenu
|
||||
* Progress = ProgressBar
|
||||
* Im = Image
|
||||
* G = Graph
|
||||
* Fr = Frame
|
||||
* Sl = Slider
|
||||
* Button - Allow button color background to be specified as None. This will cause the theme's color to be auto chosen as background
|
||||
* Image.DrawArc - fill_color parameter added
|
||||
* Column - update now uses the expand information so that a column will re-pack correctly when made invisible to visible. Added fill parm to pack call
|
||||
* New Window parameters:
|
||||
* enable_close_attempted_event=False
|
||||
* titlebar_background_color=None
|
||||
* titlebar_text_color=None
|
||||
* titlebar_font=None
|
||||
* titlebar_icon=None
|
||||
* use_custom_titlebar=None
|
||||
* Removed "Faking Timeout" print because the state that triggered it can be reached using a normal auto-closing window
|
||||
* Can now intercept window close attempts when the user clicks X
|
||||
* If you don't want "X" to close the window, set enable_close_attempted_event to True when creating window
|
||||
* When enabled a WINDOW_CLOSE_ATTEMPTED_EVENT will be returned from read instead of WIN_CLOSED
|
||||
* Multiple aliases for the key: WINDOW_CLOSE_ATTEMPTED_EVENT, WIN_X_EVENT, WIN_CLOSE_ATTEMPTED_EVENT
|
||||
* New Window property - key_dict
|
||||
* Returns the "All keys dictionary" which has keys and elements for all elements that have keys specified
|
||||
* pin helper function got 2 new parameters - expand_x=None, expand_y=None. Was needed for the Titlebar element
|
||||
* no_titlebar implementation changed on Linux. Not using "splash" and instead using "dock". Was needed to get the minimize/restore to work
|
||||
* New set_options parameters in support of custom Titlebar element
|
||||
* use_custom_titlebar=None
|
||||
* titlebar_background_color=None
|
||||
* titlebar_text_color=None
|
||||
* titlebar_font=None
|
||||
* titlebar_icon=None
|
||||
* get_globals removed - not required for normal PySimpleGUI. Was only used by patched packages which will need to fork PySimpleGUI for this feature.
|
||||
* popup - support for custom titlebar!
|
||||
* Changed from pathlib to os.path
|
||||
|
||||
|
||||
|
||||
## Upcoming
|
||||
|
||||
The future for PySimpleGUI looks bright!
|
||||
|
||||
The overall roadmap is a simple one:
|
||||
* Continue to build-out the tkinter port
|
||||
* Continue to bring features forward from the tkinter port to the other ports (Qt, WxPython, Remi)
|
||||
* Add mobile applications (native built applications instead of PyDriod3 that's used today)
|
||||
|
||||
|
||||
|
||||
|
@ -1292,7 +1658,7 @@ In addition to the ports there is ongoing work with educators that want to bring
|
|||
|
||||
It's a recipe for success if done right. PySimpleGUI has completed the "Make it run" phase. It's far from "right" in many ways. These are being worked on. The module has historically been particularly poor for PEP8 compliance. It was a learning exercise that turned into a somewhat complete GUI solution for lightweight problems.
|
||||
|
||||
While the internals to PySimpleGUI are a tad sketchy, the public interfaces into the SDK are more strictly defined and comply with PEP8 naming conventions. A set of "PEP8 Bindings" was released in summar 2019 to ensure the externally facing interfaces all adhere to PEP8 names.
|
||||
While the internals to PySimpleGUI are a tad sketchy, the public interfaces into the SDK are more strictly defined and comply with PEP8 naming conventions. A set of "PEP8 Bindings" was released in summer of 2019 to ensure the externally facing interfaces all adhere to PEP8 names.
|
||||
|
||||
Please log bugs and suggestions **only on the PySimpleGUI GitHub**! It will only make the code stronger and better in the end, a good thing for us all, right? Logging them elsewhere doesn't enable the core developer and other PySimpleGUI users to help. To make matters worse, you may get bad advice from other sites because there are simply not many PySimpleGUI experts, yet.
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,221 @@
|
|||
C:\Python\Anaconda3\python.exe C:/Users/mike/AppData/Roaming/JetBrains/PyCharmCE2020.3/scratches/scratch_1152.py
|
||||
Go {'-IN-': 'C:\\Python\\PycharmProjects\\PSG\\DemoPrograms\\screenshots', 'Browse': ''}
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
File diff suppressed because it is too large
Load Diff
|
@ -1,381 +0,0 @@
|
|||
import re,datetime,time,os,platform,json,PySimpleGUI as sg; from subprocess import Popen; from make_real_readme import main
|
||||
cd = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
def readfile(filename):
|
||||
with open(filename, 'r', encoding='utf-8') as ff: return ff.read()
|
||||
def writefile(fpath, content):
|
||||
with open(fpath, 'w', encoding='utf-8') as ff: ff.write(content)
|
||||
def writejson(a_path:str, a_dict:dict) -> None:
|
||||
with open(a_path, 'w', encoding='utf-8') as output_file: json.dump(a_dict, output_file, ensure_ascii=False, indent=2)
|
||||
def readjson(a_path:str) -> dict:
|
||||
with open(a_path, 'r', encoding='utf-8') as f: return json.load(f)
|
||||
|
||||
|
||||
def openfile(a_path):
|
||||
# File exists?
|
||||
if not os.path.exists(a_path): return sg.Popup(f"Error! This file doesn't exists: {a_path}")
|
||||
|
||||
# check: OS
|
||||
if 'Windows' in platform.system():
|
||||
os.startfile(a_path)
|
||||
|
||||
elif 'Linux' in platform.system():
|
||||
Popen(f'exo-open "{a_path}"', shell=True)
|
||||
|
||||
def opendir(a_path):
|
||||
# Folder exists?
|
||||
if not os.path.exists(a_path): return sg.Popup(f"Error! This directory doesn't exists: {a_path}")
|
||||
|
||||
# check: OS
|
||||
if 'Windows' in platform.system():
|
||||
os.startfile(a_path)
|
||||
|
||||
elif 'Linux' in platform.system():
|
||||
Popen(f'exo-open --launch FileManager --working-directory "{a_path}"', shell=True)
|
||||
|
||||
|
||||
########################################################################
|
||||
# __ _ _ #
|
||||
# / _(_) | | #
|
||||
# __ __ ___ ___ _ __ | |_ _ __ _ | |__ ___ _ __ ___ #
|
||||
# \ \ / / / __/ _ \| '_ \| _| |/ _` | | '_ \ / _ \ '__/ _ \ #
|
||||
# \ V / | (_| (_) | | | | | | | (_| | | | | | __/ | | __/ #
|
||||
# \_/ \___\___/|_| |_|_| |_|\__, | |_| |_|\___|_| \___| #
|
||||
# __/ | #
|
||||
# |___/ #
|
||||
########################################################################
|
||||
def load_configs(): return readjson(os.path.join(cd, 'app_configs.json'))
|
||||
def save_configs(a_config:dict): writejson(os.path.join(cd, 'app_configs.json'), a_config)
|
||||
|
||||
|
||||
|
||||
APP_CONFIGS = load_configs()
|
||||
README_OFILENAME = APP_CONFIGS['README_FILENAME']
|
||||
CALL_REFERENCE_OFILENAME = APP_CONFIGS['CALL_REFERENCE_FILENAME']
|
||||
|
||||
|
||||
##-#-#-# ##-#-#-#
|
||||
# Post-process logic
|
||||
##-#-#-# ##-#-#-#
|
||||
insert_md_section_for__class_methods = False
|
||||
remove_repeated_sections_classmethods = False
|
||||
|
||||
class BESTLOG(object):
|
||||
def __init__(self, filename):
|
||||
# my_file = logging.FileHandler(filename, mode='w')
|
||||
# my_file.setLevel(logging.DEBUG)
|
||||
# my_file.setFormatter(logging.Formatter('%(asctime)s>%(levelname)s: %(message)s'))
|
||||
# logger = logging.getLogger(__name__)
|
||||
# logger.setLevel(logging.DEBUG)
|
||||
# logger.addHandler(my_file)
|
||||
self.filename = filename
|
||||
self.json_name = filename + '.json'
|
||||
self.error_list = []
|
||||
self.warning_list = []
|
||||
self.info_list = []
|
||||
self.debug_list = []
|
||||
self.tick_amount=1
|
||||
self.names = self.messages_names = 'error warning info debug'.split(' ')
|
||||
|
||||
def tick(self):
|
||||
self.tick_amount+=1
|
||||
return self.tick_amount
|
||||
|
||||
#######################################################################
|
||||
# __ _ _ _ #
|
||||
# / _| | | (_) | #
|
||||
# | |_ ___ _ __ | |_ _ __ __ _ _ __ ___ _ __ _| | ___ _ __ #
|
||||
# | _/ _ \| '__| | __| '__/ _` | '_ \/ __| '_ \| | |/ _ \ '__| #
|
||||
# | || (_) | | | |_| | | (_| | | | \__ \ |_) | | | __/ | #
|
||||
# |_| \___/|_| \__|_| \__,_|_| |_|___/ .__/|_|_|\___|_| #
|
||||
# | | #
|
||||
# |_| #
|
||||
#######################################################################
|
||||
def error(self, m): self.error_list.append([self.tick(), m])
|
||||
def warning(self, m): self.warning_list.append([self.tick(), m])
|
||||
def info(self, m): self.info_list.append([self.tick(), m])
|
||||
def debug(self, m): self.debug_list.append([self.tick(), m])
|
||||
|
||||
##########################################
|
||||
# __ #
|
||||
# / _| #
|
||||
# | |_ ___ _ __ _ __ ___ ___ #
|
||||
# | _/ _ \| '__| | '_ ` _ \ / _ \ #
|
||||
# | || (_) | | | | | | | | __/ #
|
||||
# |_| \___/|_| |_| |_| |_|\___| #
|
||||
# #
|
||||
# #
|
||||
##########################################
|
||||
def tolist(self): return zip([self.error_list, self.warning_list, self.info_list, self.debug_list], self.names)
|
||||
def todict(self): return {'error' : self.error_list, 'warning' : self.warning_list, 'info' : self.info_list, 'debug' : self.debug_list}
|
||||
|
||||
def save(self):
|
||||
all_messages_list = []
|
||||
for messages, message_type in self.tolist():
|
||||
all_messages_list.extend([{'message_type' : message_type, 'message_text' : m_text, 'message_time' : m_time} for m_time, m_text in messages])
|
||||
|
||||
# sort messages on time
|
||||
all_messages_list = sorted(all_messages_list, key=lambda x: x['message_time'])
|
||||
|
||||
# convert time
|
||||
# for i in all_messages_list: i['message_time'] = i['message_time'].strftime('%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
writejson(self.json_name, all_messages_list)
|
||||
def load(self, **kw):
|
||||
'''
|
||||
return dict with messages
|
||||
|
||||
kw = {
|
||||
use_psg_color : bool
|
||||
show_time : bool
|
||||
}
|
||||
'''
|
||||
|
||||
# plan:
|
||||
# read json, convert time
|
||||
|
||||
# read
|
||||
all_messages_list = readjson(self.json_name)
|
||||
# convert time
|
||||
# for i in all_messages_list: i['message_time'] = datetime.datetime.strptime(i['message_time'], '%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
def format_message(message):
|
||||
if kw['show_time']:
|
||||
return str(message['message_time']) + ':' + message['message_text']
|
||||
else:
|
||||
return message['message_text']
|
||||
|
||||
|
||||
#=========#
|
||||
# 4 lists #
|
||||
#=========#
|
||||
error_list = [i for i in all_messages_list if i['message_type'] == 'error']
|
||||
warning_list = [i for i in all_messages_list if i['message_type'] == 'warning']
|
||||
info_list = [i for i in all_messages_list if i['message_type'] == 'info']
|
||||
debug_list = [i for i in all_messages_list if i['message_type'] == 'debug']
|
||||
|
||||
|
||||
#=================#
|
||||
# and 1 more list #
|
||||
#=================#
|
||||
colors = {'warning' : 'magenta', 'info' : 'black'}
|
||||
warning_info_ = []
|
||||
for message in sorted(warning_list + info_list, key=lambda x: x['message_time']):
|
||||
if kw['use_psg_color']:
|
||||
warning_info_.append([ format_message(message),
|
||||
colors.get(message['message_type']) ])
|
||||
else:
|
||||
warning_info_.append(format_message(message))
|
||||
|
||||
error_list = map(format_message, error_list)
|
||||
warning_list = map(format_message, warning_list)
|
||||
info_list = map(format_message, info_list)
|
||||
debug_list = map(format_message, debug_list)
|
||||
|
||||
return error_list, warning_list, info_list, debug_list, warning_info_
|
||||
|
||||
def compile_call_ref(output_filename='output/LoG_call_ref', **kw):
|
||||
''' Compile a "5_call_reference.md" file'''
|
||||
|
||||
log_obj = BESTLOG(os.path.join(cd, output_filename))
|
||||
|
||||
main(logger=log_obj,
|
||||
main_md_file='markdown input files/5_call_reference.md',
|
||||
insert_md_section_for__class_methods=insert_md_section_for__class_methods,
|
||||
remove_repeated_sections_classmethods=remove_repeated_sections_classmethods,
|
||||
files_to_include=[],
|
||||
output_name=CALL_REFERENCE_OFILENAME,
|
||||
delete_html_comments=True)
|
||||
log_obj.save()
|
||||
return log_obj.load(**kw)
|
||||
|
||||
def compile_readme(output_filename='output/LoG', **kw):
|
||||
''' Compile a "2_readme.md" file'''
|
||||
log_obj = BESTLOG(os.path.join(cd, output_filename))
|
||||
main(logger=log_obj,
|
||||
insert_md_section_for__class_methods=insert_md_section_for__class_methods,
|
||||
remove_repeated_sections_classmethods=remove_repeated_sections_classmethods,
|
||||
files_to_include=[0, 1, 2, 3],
|
||||
output_name=README_OFILENAME,
|
||||
delete_html_comments=True)
|
||||
log_obj.save()
|
||||
return log_obj.load(**kw)
|
||||
|
||||
def compile_all_stuff(**kw):
|
||||
'''
|
||||
Compile a "2_ and 5_" .md filess
|
||||
return output from them
|
||||
'''
|
||||
result_readme = compile_readme(**kw)
|
||||
result_call_ref = compile_call_ref(**kw)
|
||||
return result_readme, result_call_ref
|
||||
|
||||
|
||||
########################################
|
||||
# _____ #
|
||||
# | __ \ #
|
||||
# | |__) |__ _ __ _ _ _ __ #
|
||||
# | ___/ _ \| '_ \| | | | '_ \ #
|
||||
# | | | (_) | |_) | |_| | |_) | #
|
||||
# |_| \___/| .__/ \__,_| .__/ #
|
||||
# | | | | #
|
||||
# |_| |_| #
|
||||
########################################
|
||||
|
||||
def md2psg(target_text):
|
||||
# target = 'This is **bold** and *italic* words'
|
||||
# V
|
||||
# sg.T('This is '), sg.T('bold', font=...bold), ...'
|
||||
|
||||
# imports
|
||||
from collections import namedtuple
|
||||
spec = namedtuple('spec', 'char text'.split(' '))
|
||||
|
||||
# START
|
||||
# =====
|
||||
parts = re.compile(r'([\*]{1,2})([\s\S]*?)([\*]{1,2})', flags=re.M|re.DOTALL).split(target_text)
|
||||
chuncks, skip_this = [], 0
|
||||
for index, part in enumerate(parts):
|
||||
if skip_this != 0:
|
||||
skip_this -= 1; continue
|
||||
|
||||
if part not in ['*', '**']: chuncks.append(part)
|
||||
else:
|
||||
skip_this = 2
|
||||
chuncks.append(spec(part, parts[index+1]))
|
||||
|
||||
font_norm = ('Mono 13 ') # (*sg.DEFAULT_FONT, 'italic')
|
||||
font_bold = ('Mono 13 italic') # (*sg.DEFAULT_FONT, 'italic')
|
||||
font_ita = ('Mono 13 bold') # (*sg.DEFAULT_FONT, 'bold')
|
||||
|
||||
list_of_Ts = []
|
||||
for chunck in chuncks:
|
||||
if type(chunck) is str: list_of_Ts.append(sg.T(chunck, font=font_norm, size=(len(chunck), 1), pad=(0,0)))
|
||||
elif type(chunck) is spec:
|
||||
if chunck.char == '*': list_of_Ts.append(sg.T(chunck.text, font=font_ita, pad=(0,0), size=(len(chunck.text), 1)))
|
||||
if chunck.char == '**': list_of_Ts.append(sg.T(chunck.text, font=font_bold, pad=(0,0), size=(len(chunck.text), 1)))
|
||||
return list_of_Ts
|
||||
|
||||
|
||||
def mini_GUI():
|
||||
my_font = ("Helvetica", 12)
|
||||
my_font2 = ("Helvetica", 12, "bold")
|
||||
my_font3 = ("Helvetica", 15, "bold")
|
||||
my_font4 = ("Mono", 18, "bold")
|
||||
|
||||
def make_tab(word):
|
||||
return [[
|
||||
sg.Column(layout=[
|
||||
[sg.T('debug', font=my_font, text_color='blue')],
|
||||
[sg.ML(size=(70-15, 15), key=f'-{word}-debug-')],
|
||||
[sg.T('error', font=my_font, text_color='red')],
|
||||
[sg.ML(size=(70-15, 15), key=f'-{word}-error-')],
|
||||
]),
|
||||
sg.T(' '), sg.Column(layout=[
|
||||
[sg.T('warning', font=my_font2)],
|
||||
[sg.ML(size=(70-12, 15), key=f'-{word}-warning-')],
|
||||
[sg.T('info', font=my_font2)],
|
||||
[sg.ML(size=(70-12, 15), key=f'-{word}-info-')],
|
||||
]),
|
||||
sg.Column(layout=[
|
||||
[sg.T('warning_info', font=my_font3)],
|
||||
[sg.ML(size=(110, 42-8), key=f'-{word}-warning_info-')],
|
||||
]),
|
||||
]]
|
||||
layout = [
|
||||
[ sg.TabGroup( [[
|
||||
sg.Tab('README', make_tab('README')),
|
||||
sg.Tab('CALL_REF', make_tab('CALL_REF'))
|
||||
]]
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
window = sg.Window('We are live! Again! --- ' + 'Completed making {}, {}'.format(os.path.basename(README_OFILENAME), os.path.basename(CALL_REFERENCE_OFILENAME)), [
|
||||
[sg.T(size=(25,1), font=my_font, key='-compile-time-')],
|
||||
[sg.T(f'The PySimpleGUI module being processed is {sg}')],
|
||||
[
|
||||
sg.B('Run again (F1)', key='-run-')
|
||||
,sg.CB('show time in logs (F2)', False, key='show_time')
|
||||
,sg.CB('Logs with Color (F3)', True, key='use_psg_color')
|
||||
,sg.B('open call ref', key='-open_call_ref-')
|
||||
,sg.B('open readme.txt', key='-open_readme.txt-')
|
||||
,sg.B('open "db folder"', key='-open_db_folder-')
|
||||
,sg.T(' '*30)
|
||||
,sg.Col([
|
||||
# [sg.T('output name for call_ref markdown file', key=(15,1)), sg.I(key='')],
|
||||
[*md2psg('markdown outputFileName *FOR* **readme **: '), sg.I(README_OFILENAME, key='md1'), sg.B('open in explorer', key='open in explorer_readme')],
|
||||
[*md2psg('markdown outputFileName *FOR* **call ref**: '), sg.I(CALL_REFERENCE_OFILENAME, key='md2'), sg.B('open in explorer', key='open in explorer_calref')]
|
||||
])
|
||||
]
|
||||
,*layout
|
||||
], resizable=True, finalize=True, location=(0,0), return_keyboard_events = True)
|
||||
|
||||
def update_time_in_GUI(): window['-compile-time-'](datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S.%f'))
|
||||
|
||||
def update_compilation_in_psg(values):
|
||||
# get results
|
||||
result_readme, result_call_ref = compile_all_stuff(use_psg_color=values['use_psg_color'], show_time=values['show_time'])
|
||||
|
||||
# DO 2_readme
|
||||
window['-README-error-']('\n'.join(result_readme[0]))
|
||||
window['-README-warning-']('\n'.join(result_readme[1]))
|
||||
window['-README-info-']('\n'.join(result_readme[2]))
|
||||
window['-README-debug-']('\n'.join(result_readme[3]))
|
||||
# /// colors warning_info
|
||||
window['-README-warning_info-'].update('')
|
||||
if values['use_psg_color']:
|
||||
for text, color in result_readme[-1]:
|
||||
window['-README-warning_info-'].print(text, text_color=color)
|
||||
else:
|
||||
window['-README-warning_info-']('\n'.join(result_readme[-1]))
|
||||
|
||||
# DO 5_cal_ref
|
||||
window['-CALL_REF-error-']('\n'.join(result_call_ref[0]))
|
||||
window['-CALL_REF-warning-']('\n'.join(result_call_ref[1]))
|
||||
window['-CALL_REF-info-']('\n'.join(result_call_ref[2]))
|
||||
window['-CALL_REF-debug-']('\n'.join(result_call_ref[3]))
|
||||
# /// colors warning_info
|
||||
window['-CALL_REF-warning_info-'].update('')
|
||||
if values['use_psg_color']:
|
||||
for text, color in result_call_ref[-1]:
|
||||
window['-CALL_REF-warning_info-'].print(text, text_color=color)
|
||||
else:
|
||||
window['-CALL_REF-warning_info-']('\n'.join(result_call_ref[-1]))
|
||||
|
||||
# ~~~~~~~~~~~~
|
||||
# GUI updating
|
||||
# ~~~~~~~~~~~~
|
||||
update_time_in_GUI()
|
||||
|
||||
update_compilation_in_psg({'use_psg_color':not False, 'show_time':False})
|
||||
while True:
|
||||
event, values = window()
|
||||
|
||||
# print(values)
|
||||
if event in ('Exit', None):
|
||||
APP_CONFIGS['README_FILENAME'], APP_CONFIGS['CALL_REFERENCE_FILENAME'] = window['md1'].get(), window['md2'].get()
|
||||
save_configs(APP_CONFIGS)
|
||||
break
|
||||
|
||||
print('PSG event>', event)
|
||||
|
||||
# buttons
|
||||
if event == '-run-': update_compilation_in_psg(values)
|
||||
if event == '-open_readme.txt-': openfile(README_OFILENAME)
|
||||
if event == '-open_call_ref-': openfile(CALL_REFERENCE_OFILENAME)
|
||||
if event == '-open_db_folder-': opendir(cd)
|
||||
if event == '-open_github_gallery-': opendir(cd)
|
||||
if event == 'open in explorer_readme': opendir(os.path.dirname(os.path.join(cd, values['md1'])))
|
||||
if event == 'open in explorer_calref': opendir(os.path.dirname(os.path.join(cd, values['md2'])))
|
||||
# hotkeys
|
||||
if 'F1' in event: update_compilation_in_psg(values)
|
||||
if 'F2' in event: window['show_time'](not values['show_time'])
|
||||
if 'F3' in event: window['use_psg_color'](not values['use_psg_color'])
|
||||
|
||||
window.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
mini_GUI()
|
||||
# sg.PopupScrolled('Completed making {}'.format(README_OFILENAME), ''.join(lines), size=(80,50))
|
|
@ -1,8 +1,6 @@
|
|||
import PySimpleGUI as sg
|
||||
import PySimpleGUI;sg = PySimpleGUI
|
||||
import datetime, inspect
|
||||
|
||||
module_to_process = sg
|
||||
|
||||
"""
|
||||
Create All Possible Tags
|
||||
Will output to STDOUT all of the different tags for classes, members and functions for a given PySimpleGUI.py
|
||||
|
@ -29,7 +27,7 @@ def valid_field(pair):
|
|||
|
||||
|
||||
# # ]
|
||||
psg_members = [i for i in inspect.getmembers(module_to_process) if valid_field(i)] # ]
|
||||
psg_members = [i for i in inspect.getmembers(PySimpleGUI) if valid_field(i)] # ]
|
||||
psg_funcs = [o[0] for o in psg_members if inspect.isfunction(o[1])] # ] Grabing PSG objects
|
||||
psg_classes = [o for o in psg_members if inspect.isclass(o[1])] # ]
|
||||
# psg_props = [o for o in psg_members if type(o[1]).__name__ == 'property'] # ]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
py 'RUN_ME and START_HERE.py'
|
Loading…
Reference in New Issue