py2gui 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
py2gui/py2gui.py CHANGED
@@ -1,3 +1,7 @@
1
+ """
2
+ Py2GUI - Enhanced Python Terminal-style GUI
3
+ Fixed version: Fixed potential errors and issues
4
+ """
1
5
  import tkinter as tk
2
6
  from tkinter import scrolledtext, simpledialog, Menu, Frame, Entry, Button, StringVar, font
3
7
  import queue
@@ -6,11 +10,14 @@ import traceback
6
10
  import re
7
11
  import json
8
12
  import os
9
- from typing import Callable, Any, Optional, List, Tuple, Dict
13
+ import sys
14
+ from typing import Callable, Any, Optional, List, Tuple, Dict, Set
15
+ import warnings
10
16
 
11
17
 
12
18
  class Py2GUI:
13
19
  def __init__(self, title: str = "Py2GUI", width: int = 80, height: int = 20, config_file: str = "config.json"):
20
+ """Initialize Py2GUI instance"""
14
21
  self.root = tk.Tk()
15
22
  self.root.title(title)
16
23
  self.root.resizable(True, True)
@@ -22,7 +29,7 @@ class Py2GUI:
22
29
  # Load configuration
23
30
  self.config = self._load_config()
24
31
 
25
- # Extended ANSI color configuration with full 256 colors
32
+ # Extended ANSI color configuration
26
33
  self.ansi_colors = {
27
34
  # Basic colors
28
35
  '30': '#000000', # Black
@@ -54,7 +61,7 @@ class Py2GUI:
54
61
  '46': '#00ffff', # Cyan background
55
62
  '47': '#ffffff', # White background
56
63
 
57
- # Extended 256 colors (common ones)
64
+ # Extended 256 colors
58
65
  '38;5;0': '#000000', # Black
59
66
  '38;5;1': '#800000', # Dark red
60
67
  '38;5;2': '#008000', # Dark green
@@ -82,14 +89,14 @@ class Py2GUI:
82
89
  '48;5;6': '#008080', # Dark cyan background
83
90
  '48;5;7': '#c0c0c0', # Light gray background
84
91
 
85
- # True color support (RGB)
92
+ # True Color support
86
93
  '38;2;0;0;0': '#000000', # Black
87
94
  '38;2;255;0;0': '#ff0000', # Red
88
95
  '38;2;0;255;0': '#00ff00', # Green
89
96
  '38;2;0;0;255': '#0000ff', # Blue
90
97
  }
91
98
 
92
- # Color name to hex mapping for display_colored method
99
+ # Color name to hex mapping
93
100
  self.color_name_to_hex = {
94
101
  'black': '#000000',
95
102
  'red': '#ff0000',
@@ -116,17 +123,11 @@ class Py2GUI:
116
123
  }
117
124
 
118
125
  # Available fonts
119
- self.available_fonts = font.families()
120
-
121
- # ANSI style configuration
122
- self.ansi_styles = {
123
- '0': {'font': ('Courier', 10, 'normal'), 'fg': 'white', 'bg': 'black'}, # Reset
124
- '1': {'font': ('Courier', 10, 'bold')}, # Bold
125
- '3': {'font': ('Courier', 10, 'italic')}, # Italic
126
- '4': {'underline': True}, # Underline
127
- '7': {'fg': 'black', 'bg': 'white'}, # Reverse video
128
- '9': {'strikethrough': True}, # Strikethrough
129
- }
126
+ try:
127
+ self.available_fonts = font.families()
128
+ except Exception:
129
+ self.available_fonts = ["Courier", "Consolas", "Monaco", "Menlo"]
130
+ warnings.warn(f"Could not load font families, using fallback fonts: {self.available_fonts}")
130
131
 
131
132
  # Current text style state
132
133
  self.current_style = {
@@ -138,12 +139,12 @@ class Py2GUI:
138
139
  }
139
140
 
140
141
  # Defined text tags
141
- self.tag_names = set()
142
+ self.tag_names: Set[str] = set()
142
143
 
143
- # Handle window closure
144
+ # Handle window close
144
145
  self.root.protocol("WM_DELETE_WINDOW", self.exit)
145
146
 
146
- # Main frame to hold everything
147
+ # Main frame
147
148
  self.main_frame = Frame(self.root)
148
149
  self.main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
149
150
 
@@ -168,7 +169,7 @@ class Py2GUI:
168
169
  background="black"
169
170
  )
170
171
 
171
- # Configure color tags with hex color codes (excluding disabled colors)
172
+ # Configure color tags
172
173
  for code, color_hex in self.ansi_colors.items():
173
174
  # Skip disabled colors
174
175
  if 'disabled_colors' in self.config and code in self.config['disabled_colors']:
@@ -176,11 +177,12 @@ class Py2GUI:
176
177
 
177
178
  tag_name = f"ansi_{code}"
178
179
  if (code.startswith('3') and ';' not in code) or code.startswith('38'):
179
- # Foreground colors
180
+ # Foreground color
180
181
  self.text_area.tag_configure(tag_name, foreground=color_hex)
181
182
  elif code.startswith('4') or code.startswith('48'):
182
- # Background colors
183
+ # Background color
183
184
  self.text_area.tag_configure(tag_name, background=color_hex)
185
+ self.tag_names.add(tag_name)
184
186
 
185
187
  # Configure style tags
186
188
  self.text_area.tag_configure("bold", font=("Courier", 10, "bold"))
@@ -189,7 +191,11 @@ class Py2GUI:
189
191
  self.text_area.tag_configure("strikethrough", overstrike=True)
190
192
  self.text_area.tag_configure("reverse", foreground="black", background="white")
191
193
 
192
- # Terminal-style input area frame
194
+ # Add style tags to tag set
195
+ for tag in ["bold", "italic", "underline", "strikethrough", "reverse"]:
196
+ self.tag_names.add(tag)
197
+
198
+ # Terminal style input area frame
193
199
  self.input_frame = Frame(self.main_frame)
194
200
  self.input_frame.pack(fill=tk.X, padx=5, pady=5)
195
201
 
@@ -197,7 +203,7 @@ class Py2GUI:
197
203
  self.input_label = tk.Label(self.input_frame, text=">> ", font=("Courier", 10), fg="white", bg="black")
198
204
  self.input_label.pack(side=tk.LEFT, padx=(0, 5))
199
205
 
200
- # Terminal-style input entry
206
+ # Terminal style input field
201
207
  self.input_var = StringVar()
202
208
  self.input_entry = Entry(
203
209
  self.input_frame,
@@ -218,19 +224,17 @@ class Py2GUI:
218
224
  )
219
225
  self.send_button.pack(side=tk.LEFT)
220
226
 
221
- # Input queue for user_write
227
+ # Input queues
222
228
  self.input_queue = queue.Queue()
223
-
224
- # Input queue for user_type_in
225
229
  self.type_in_queue = queue.Queue()
226
230
 
227
- # Bind Enter key to send input
231
+ # Bind Enter key
228
232
  self.input_entry.bind('<Return>', self._on_enter_pressed)
229
233
 
230
- # Menus
234
+ # Create menus
231
235
  self._setup_menus()
232
236
 
233
- def _load_config(self) -> Dict:
237
+ def _load_config(self) -> Dict[str, Any]:
234
238
  """Load configuration from JSON file"""
235
239
  default_config = {
236
240
  "disabled_menus": [],
@@ -242,19 +246,29 @@ class Py2GUI:
242
246
 
243
247
  try:
244
248
  if os.path.exists(self.config_file):
245
- with open(self.config_file, 'r') as f:
249
+ with open(self.config_file, 'r', encoding='utf-8') as f:
246
250
  config = json.load(f)
247
251
  # Merge with default config
248
252
  for key, value in default_config.items():
249
253
  if key not in config:
250
254
  config[key] = value
251
255
  return config
256
+ except (json.JSONDecodeError, IOError, OSError) as e:
257
+ self._safe_print(f"Error loading config file: {e}")
252
258
  except Exception as e:
253
- print(f"Error loading config file: {e}")
259
+ self._safe_print(f"Unexpected error loading config: {e}")
254
260
 
255
261
  return default_config
256
262
 
263
+ def _safe_print(self, message: str) -> None:
264
+ """Safely print message (for initialization and error handling)"""
265
+ try:
266
+ print(message, file=sys.stderr)
267
+ except Exception:
268
+ pass # Ignore print errors
269
+
257
270
  def _setup_menus(self) -> None:
271
+ """Set up menu system"""
258
272
  menubar = Menu(self.root)
259
273
  self.root.config(menu=menubar)
260
274
 
@@ -276,7 +290,6 @@ class Py2GUI:
276
290
  view_menu = Menu(menubar, tearoff=0)
277
291
  menubar.add_cascade(label="View", menu=view_menu)
278
292
 
279
- # Add view menu items based on configuration
280
293
  if 'disabled_views' not in self.config or 'Focus Input' not in self.config['disabled_views']:
281
294
  view_menu.add_command(label="Focus Input", command=self.focus_input)
282
295
 
@@ -297,12 +310,12 @@ class Py2GUI:
297
310
 
298
311
  def _parse_ansi_codes(self, text: str) -> List[Tuple[str, List[str]]]:
299
312
  """Parse ANSI escape sequences in text"""
300
- # ANSI escape sequence regex
301
- ansi_pattern = re.compile(r'(\033\[[\d;]*m)')
313
+ # ANSI escape sequence regex pattern
314
+ ansi_pattern = re.compile(r'(\x1b\[[\d;]*m)')
302
315
 
303
316
  parts = []
304
317
  last_end = 0
305
- current_codes = []
318
+ current_codes: List[str] = []
306
319
 
307
320
  for match in ansi_pattern.finditer(text):
308
321
  # Add normal text
@@ -313,7 +326,7 @@ class Py2GUI:
313
326
 
314
327
  # Parse ANSI code
315
328
  ansi_code = match.group(0)
316
- code_str = ansi_code[2:-1] # Remove \033[ and m
329
+ code_str = ansi_code[2:-1] # Remove \x1b[ and m
317
330
 
318
331
  if code_str == '':
319
332
  # Reset all attributes
@@ -326,10 +339,7 @@ class Py2GUI:
326
339
  current_codes = ['0']
327
340
  elif code in ['1', '3', '4', '7', '9']:
328
341
  # Style codes
329
- if code in current_codes:
330
- # Remove duplicate style codes
331
- pass
332
- else:
342
+ if code not in current_codes:
333
343
  if code == '1' and '22' in current_codes:
334
344
  current_codes.remove('22')
335
345
  current_codes.append(code)
@@ -340,11 +350,10 @@ class Py2GUI:
340
350
  current_codes.remove(reset_map[code])
341
351
  elif code in self.ansi_colors or code.startswith('38;') or code.startswith('48;'):
342
352
  # Color codes
343
- # Skip disabled colors
344
353
  if 'disabled_colors' in self.config and code in self.config['disabled_colors']:
345
354
  continue
346
355
 
347
- # Remove same type of color codes
356
+ # Remove same type color codes
348
357
  if code in ['30', '31', '32', '33', '34', '35', '36', '37',
349
358
  '90', '91', '92', '93', '94', '95', '96', '97']:
350
359
  # Remove other basic foreground colors
@@ -369,7 +378,7 @@ class Py2GUI:
369
378
  current_codes.remove(c)
370
379
  current_codes.append(code)
371
380
 
372
- # Add the last part of text
381
+ # Add remaining text
373
382
  if last_end < len(text):
374
383
  remaining_text = text[last_end:]
375
384
  if remaining_text:
@@ -397,9 +406,11 @@ class Py2GUI:
397
406
  if 'disabled_colors' in self.config and code in self.config['disabled_colors']:
398
407
  continue
399
408
  # Color tags
400
- tags.append(f"ansi_{code}")
409
+ tag_name = f"ansi_{code}"
410
+ if tag_name in self.tag_names:
411
+ tags.append(tag_name)
401
412
 
402
- return tags
413
+ return tags if tags else ['default']
403
414
 
404
415
  def _process_escape_sequences(self, text: str) -> str:
405
416
  """Process escape sequences like \n, \t, etc."""
@@ -422,261 +433,271 @@ class Py2GUI:
422
433
  return text
423
434
 
424
435
  def display_paragraph(self, text: str, parse_ansi: bool = True, font_family: Optional[str] = None,
425
- font_size: Optional[int] = None, font_style: Optional[str] = None) -> None:
426
- """Thread-safe display of paragraphs with escape sequence processing and no auto newline"""
436
+ font_size: Optional[int] = None, font_style: Optional[str] = None) -> None:
437
+ """Thread-safe display paragraph (no auto newline)"""
427
438
  def _update():
428
- self.text_area.config(state=tk.NORMAL)
429
-
430
- # Process escape sequences
431
- text_processed = self._process_escape_sequences(text)
432
-
433
- # Check for custom font settings
434
- font_tags = []
435
- if font_family or font_size or font_style:
436
- # Create a unique tag for this font combination
437
- font_family_val = font_family or "Courier"
438
- font_size_val = font_size or 10
439
- font_style_val = font_style or "normal"
440
- font_key = f"font_{font_family_val}_{font_size_val}_{font_style_val}"
439
+ try:
440
+ self.text_area.config(state=tk.NORMAL)
441
441
 
442
- if font_key not in self.tag_names:
443
- self.text_area.tag_configure(font_key, font=(font_family_val, font_size_val, font_style_val))
444
- self.tag_names.add(font_key)
445
- font_tags.append(font_key)
446
-
447
- if parse_ansi and ('\033[' in text_processed or '\x1b[' in text_processed):
448
- # Parse and apply ANSI colors
449
- parts = self._parse_ansi_codes(text_processed)
442
+ # Process escape sequences
443
+ text_processed = self._process_escape_sequences(text)
450
444
 
451
- for part_text, codes in parts:
452
- tags = self._get_tags_for_codes(codes)
453
- if not tags:
454
- tags = ['default']
445
+ # Check custom font settings
446
+ font_tags = []
447
+ if font_family or font_size or font_style:
448
+ # Create unique tag for font combination
449
+ font_family_val = font_family or "Courier"
450
+ font_size_val = font_size or 10
451
+ font_style_val = font_style or "normal"
452
+ font_key = f"font_{font_family_val}_{font_size_val}_{font_style_val}"
455
453
 
456
- # Add font tags if specified
457
- if font_tags:
458
- tags = font_tags + tags
454
+ if font_key not in self.tag_names:
455
+ self.text_area.tag_configure(font_key,
456
+ font=(font_family_val, font_size_val, font_style_val))
457
+ self.tag_names.add(font_key)
458
+ font_tags.append(font_key)
459
+
460
+ if parse_ansi and ('\x1b[' in text_processed or '\033[' in text_processed):
461
+ # Parse and apply ANSI colors
462
+ parts = self._parse_ansi_codes(text_processed)
459
463
 
460
- # Insert text and apply tags
461
- self.text_area.insert(tk.END, part_text, tuple(tags))
462
- else:
463
- # Plain text
464
- tags = ['default']
465
- if font_tags:
466
- tags = font_tags
467
- self.text_area.insert(tk.END, text_processed, tuple(tags))
468
-
469
- self.text_area.config(state=tk.DISABLED)
470
- self.text_area.see(tk.END)
471
-
472
- self.root.after(0, _update)
464
+ for part_text, codes in parts:
465
+ tags = self._get_tags_for_codes(codes)
466
+
467
+ # Add font tags (if specified)
468
+ if font_tags:
469
+ tags = font_tags + tags
470
+
471
+ # Insert text and apply tags
472
+ self.text_area.insert(tk.END, part_text, tuple(tags))
473
+ else:
474
+ # Normal text
475
+ tags = ['default']
476
+ if font_tags:
477
+ tags = font_tags
478
+ self.text_area.insert(tk.END, text_processed, tuple(tags))
479
+
480
+ self.text_area.config(state=tk.DISABLED)
481
+ self.text_area.see(tk.END)
482
+ except tk.TclError as e:
483
+ if self.running:
484
+ self._safe_print(f"Tkinter error in display_paragraph: {e}")
485
+ except Exception as e:
486
+ if self.running:
487
+ self._safe_print(f"Error in display_paragraph: {e}")
488
+
489
+ if self.running:
490
+ self.root.after(0, _update)
473
491
 
474
492
  def display(self, text: str, parse_ansi: bool = True, font_family: Optional[str] = None,
475
- font_size: Optional[int] = None, font_style: Optional[str] = None) -> None:
476
- """Thread-safe display with ANSI color support and font customization"""
493
+ font_size: Optional[int] = None, font_style: Optional[str] = None) -> None:
494
+ """Thread-safe display text (auto newline)"""
477
495
  def _update():
478
- self.text_area.config(state=tk.NORMAL)
479
-
480
- # Check for custom font settings
481
- font_tags = []
482
- if font_family or font_size or font_style:
483
- # Create a unique tag for this font combination
484
- font_family_val = font_family or "Courier"
485
- font_size_val = font_size or 10
486
- font_style_val = font_style or "normal"
487
- font_key = f"font_{font_family_val}_{font_size_val}_{font_style_val}"
496
+ try:
497
+ self.text_area.config(state=tk.NORMAL)
488
498
 
489
- if font_key not in self.tag_names:
490
- self.text_area.tag_configure(font_key, font=(font_family_val, font_size_val, font_style_val))
491
- self.tag_names.add(font_key)
492
- font_tags.append(font_key)
493
-
494
- if parse_ansi and ('\033[' in text or '\x1b[' in text):
495
- # Parse and apply ANSI colors
496
- parts = self._parse_ansi_codes(text)
499
+ # Check custom font settings
500
+ font_tags = []
501
+ if font_family or font_size or font_style:
502
+ # Create unique tag for font combination
503
+ font_family_val = font_family or "Courier"
504
+ font_size_val = font_size or 10
505
+ font_style_val = font_style or "normal"
506
+ font_key = f"font_{font_family_val}_{font_size_val}_{font_style_val}"
507
+
508
+ if font_key not in self.tag_names:
509
+ self.text_area.tag_configure(font_key,
510
+ font=(font_family_val, font_size_val, font_style_val))
511
+ self.tag_names.add(font_key)
512
+ font_tags.append(font_key)
497
513
 
498
- for part_text, codes in parts:
499
- tags = self._get_tags_for_codes(codes)
500
- if not tags:
501
- tags = ['default']
514
+ if parse_ansi and ('\x1b[' in str(text) or '\033[' in str(text)):
515
+ # Parse and apply ANSI colors
516
+ parts = self._parse_ansi_codes(str(text))
502
517
 
503
- # Add font tags if specified
504
- if font_tags:
505
- tags = font_tags + tags
518
+ for part_text, codes in parts:
519
+ tags = self._get_tags_for_codes(codes)
520
+
521
+ # Add font tags (if specified)
522
+ if font_tags:
523
+ tags = font_tags + tags
524
+
525
+ # Insert text and apply tags
526
+ self.text_area.insert(tk.END, part_text, tuple(tags))
506
527
 
507
- # Insert text and apply tags
508
- self.text_area.insert(tk.END, part_text, tuple(tags))
509
-
510
- # Add newline
511
- if font_tags:
512
- self.text_area.insert(tk.END, "\n", tuple(font_tags))
528
+ # Add newline
529
+ if font_tags:
530
+ self.text_area.insert(tk.END, "\n", tuple(font_tags))
531
+ else:
532
+ self.text_area.insert(tk.END, "\n", 'default')
513
533
  else:
514
- self.text_area.insert(tk.END, "\n", 'default')
515
- else:
516
- # Plain text
517
- tags = ['default']
518
- if font_tags:
519
- tags = font_tags
520
- self.text_area.insert(tk.END, str(text) + "\n", tuple(tags))
521
-
522
- self.text_area.config(state=tk.DISABLED)
523
- self.text_area.see(tk.END)
524
-
525
- self.root.after(0, _update)
534
+ # Normal text
535
+ tags = ['default']
536
+ if font_tags:
537
+ tags = font_tags
538
+ self.text_area.insert(tk.END, str(text) + "\n", tuple(tags))
539
+
540
+ self.text_area.config(state=tk.DISABLED)
541
+ self.text_area.see(tk.END)
542
+ except tk.TclError as e:
543
+ if self.running:
544
+ self._safe_print(f"Tkinter error in display: {e}")
545
+ except Exception as e:
546
+ if self.running:
547
+ self._safe_print(f"Error in display: {e}")
548
+
549
+ if self.running:
550
+ self.root.after(0, _update)
526
551
 
527
552
  def display_colored(self, text: str, fg_color: Optional[str] = None, bg_color: Optional[str] = None,
528
553
  bold: bool = False, underline: bool = False, italic: bool = False,
529
554
  strikethrough: bool = False, reverse: bool = False,
530
555
  font_family: Optional[str] = None, font_size: Optional[int] = None,
531
556
  font_style: Optional[str] = None) -> None:
532
- """Directly display colored text with full color and font support"""
557
+ """Directly display colored text"""
533
558
  def _update():
534
- self.text_area.config(state=tk.NORMAL)
535
-
536
- tags = ['default']
537
-
538
- # Handle foreground color - FIXED: Check for empty strings
539
- if fg_color is not None and fg_color != "":
540
- # Check if it's a color name that needs conversion
541
- if fg_color.lower() in self.color_name_to_hex:
542
- color_value = self.color_name_to_hex[fg_color.lower()]
543
- elif fg_color.isdigit():
544
- # It's an ANSI code
545
- # Skip disabled colors
546
- if 'disabled_colors' in self.config and fg_color in self.config['disabled_colors']:
547
- # Use default color for disabled colors
548
- pass
549
- elif fg_color in self.ansi_colors:
550
- tags.append(f"ansi_{fg_color}")
551
- else:
552
- # Default to white if unknown ANSI code
553
- color_value = '#ffffff'
559
+ try:
560
+ self.text_area.config(state=tk.NORMAL)
561
+
562
+ tags = ['default']
563
+
564
+ # Process foreground color
565
+ if fg_color is not None and fg_color != "":
566
+ fg_color_lower = fg_color.lower()
567
+ if fg_color_lower in self.color_name_to_hex:
568
+ color_value = self.color_name_to_hex[fg_color_lower]
554
569
  custom_fg_tag = f"custom_fg_{color_value}"
555
570
  tags.append(custom_fg_tag)
556
571
  if custom_fg_tag not in self.tag_names:
557
572
  self.text_area.tag_configure(custom_fg_tag, foreground=color_value)
558
573
  self.tag_names.add(custom_fg_tag)
559
- elif fg_color.startswith('#') and len(fg_color) in [4, 5, 7, 9]:
560
- # It's a hex color
561
- color_value = fg_color
562
- custom_fg_tag = f"custom_fg_{color_value}"
563
- tags.append(custom_fg_tag)
564
- if custom_fg_tag not in self.tag_names:
565
- self.text_area.tag_configure(custom_fg_tag, foreground=color_value)
566
- self.tag_names.add(custom_fg_tag)
567
- elif ';' in fg_color and fg_color.startswith('38;'):
568
- # Extended ANSI color code
569
- if fg_color in self.ansi_colors:
570
- tags.append(f"ansi_{fg_color}")
571
- else:
572
- # Try to use as a named color, but only if it's not empty
573
- try:
574
- if fg_color.strip(): # Check it's not just whitespace
575
- self.text_area.tag_configure(f"custom_fg_{fg_color}", foreground=fg_color)
576
- tags.append(f"custom_fg_{fg_color}")
577
- self.tag_names.add(f"custom_fg_{fg_color}")
578
- except tk.TclError:
579
- # Fall back to default
580
- pass
581
-
582
- # Handle background color - FIXED: Check for empty strings
583
- if bg_color is not None and bg_color != "":
584
- # Check if it's a color name that needs conversion
585
- if bg_color.lower() in self.color_name_to_hex:
586
- color_value = self.color_name_to_hex[bg_color.lower()]
587
- elif bg_color.isdigit():
588
- # It's an ANSI code
589
- # Skip disabled colors
590
- if 'disabled_colors' in self.config and bg_color in self.config['disabled_colors']:
591
- # Use default color for disabled colors
592
- pass
593
- elif bg_color in self.ansi_colors:
594
- tags.append(f"ansi_{bg_color}")
574
+ elif fg_color.isdigit():
575
+ # ANSI code
576
+ if ('disabled_colors' not in self.config or
577
+ fg_color not in self.config.get('disabled_colors', [])):
578
+ if fg_color in self.ansi_colors:
579
+ tags.append(f"ansi_{fg_color}")
580
+ elif fg_color.startswith('#') and len(fg_color) in [4, 5, 7, 9]:
581
+ # Hex color
582
+ color_value = fg_color
583
+ custom_fg_tag = f"custom_fg_{color_value}"
584
+ tags.append(custom_fg_tag)
585
+ if custom_fg_tag not in self.tag_names:
586
+ self.text_area.tag_configure(custom_fg_tag, foreground=color_value)
587
+ self.tag_names.add(custom_fg_tag)
588
+ elif ';' in fg_color and fg_color.startswith('38;'):
589
+ # Extended ANSI color codes
590
+ if fg_color in self.ansi_colors:
591
+ tags.append(f"ansi_{fg_color}")
595
592
  else:
596
- # Default to black if unknown ANSI code
597
- color_value = '#000000'
593
+ # Try named color
594
+ try:
595
+ if fg_color.strip():
596
+ self.text_area.tag_configure(f"custom_fg_{fg_color}", foreground=fg_color)
597
+ tags.append(f"custom_fg_{fg_color}")
598
+ self.tag_names.add(f"custom_fg_{fg_color}")
599
+ except tk.TclError:
600
+ pass
601
+
602
+ # Process background color
603
+ if bg_color is not None and bg_color != "":
604
+ bg_color_lower = bg_color.lower()
605
+ if bg_color_lower in self.color_name_to_hex:
606
+ color_value = self.color_name_to_hex[bg_color_lower]
598
607
  custom_bg_tag = f"custom_bg_{color_value}"
599
608
  tags.append(custom_bg_tag)
600
609
  if custom_bg_tag not in self.tag_names:
601
610
  self.text_area.tag_configure(custom_bg_tag, background=color_value)
602
611
  self.tag_names.add(custom_bg_tag)
603
- elif bg_color.startswith('#') and len(bg_color) in [4, 5, 7, 9]:
604
- # It's a hex color
605
- color_value = bg_color
606
- custom_bg_tag = f"custom_bg_{color_value}"
607
- tags.append(custom_bg_tag)
608
- if custom_bg_tag not in self.tag_names:
609
- self.text_area.tag_configure(custom_bg_tag, background=color_value)
610
- self.tag_names.add(custom_bg_tag)
611
- elif ';' in bg_color and bg_color.startswith('48;'):
612
- # Extended ANSI color code
613
- if bg_color in self.ansi_colors:
614
- tags.append(f"ansi_{bg_color}")
615
- else:
616
- # Try to use as a named color, but only if it's not empty
617
- try:
618
- if bg_color.strip(): # Check it's not just whitespace
619
- self.text_area.tag_configure(f"custom_bg_{bg_color}", background=bg_color)
620
- tags.append(f"custom_bg_{bg_color}")
621
- self.tag_names.add(f"custom_bg_{bg_color}")
622
- except tk.TclError:
623
- # Fall back to default
624
- pass
625
-
626
- # Handle font
627
- if font_family or font_size or font_style:
628
- # Create a unique tag for this font combination
629
- font_family_val = font_family or "Courier"
630
- font_size_val = font_size or 10
631
- font_style_val = font_style or "normal"
632
- font_key = f"font_{font_family_val}_{font_size_val}_{font_style_val}"
612
+ elif bg_color.isdigit():
613
+ # ANSI code
614
+ if ('disabled_colors' not in self.config or
615
+ bg_color not in self.config.get('disabled_colors', [])):
616
+ if bg_color in self.ansi_colors:
617
+ tags.append(f"ansi_{bg_color}")
618
+ elif bg_color.startswith('#') and len(bg_color) in [4, 5, 7, 9]:
619
+ # Hex color
620
+ color_value = bg_color
621
+ custom_bg_tag = f"custom_bg_{color_value}"
622
+ tags.append(custom_bg_tag)
623
+ if custom_bg_tag not in self.tag_names:
624
+ self.text_area.tag_configure(custom_bg_tag, background=color_value)
625
+ self.tag_names.add(custom_bg_tag)
626
+ elif ';' in bg_color and bg_color.startswith('48;'):
627
+ # Extended ANSI color codes
628
+ if bg_color in self.ansi_colors:
629
+ tags.append(f"ansi_{bg_color}")
630
+ else:
631
+ # Try named color
632
+ try:
633
+ if bg_color.strip():
634
+ self.text_area.tag_configure(f"custom_bg_{bg_color}", background=bg_color)
635
+ tags.append(f"custom_bg_{bg_color}")
636
+ self.tag_names.add(f"custom_bg_{bg_color}")
637
+ except tk.TclError:
638
+ pass
633
639
 
634
- if font_key not in self.tag_names:
635
- self.text_area.tag_configure(font_key, font=(font_family_val, font_size_val, font_style_val))
636
- self.tag_names.add(font_key)
637
- tags.append(font_key)
638
-
639
- # Handle styles
640
- if bold:
641
- tags.append('bold')
642
- if underline:
643
- tags.append('underline')
644
- if italic:
645
- tags.append('italic')
646
- if strikethrough:
647
- tags.append('strikethrough')
648
- if reverse:
649
- tags.append('reverse')
650
-
651
- self.text_area.insert(tk.END, str(text) + "\n", tuple(tags))
652
- self.text_area.config(state=tk.DISABLED)
653
- self.text_area.see(tk.END)
654
-
655
- self.root.after(0, _update)
640
+ # Process font
641
+ if font_family or font_size or font_style:
642
+ font_family_val = font_family or "Courier"
643
+ font_size_val = font_size or 10
644
+ font_style_val = font_style or "normal"
645
+ font_key = f"font_{font_family_val}_{font_size_val}_{font_style_val}"
646
+
647
+ if font_key not in self.tag_names:
648
+ self.text_area.tag_configure(font_key,
649
+ font=(font_family_val, font_size_val, font_style_val))
650
+ self.tag_names.add(font_key)
651
+ tags.append(font_key)
652
+
653
+ # Process styles
654
+ if bold:
655
+ tags.append('bold')
656
+ if underline:
657
+ tags.append('underline')
658
+ if italic:
659
+ tags.append('italic')
660
+ if strikethrough:
661
+ tags.append('strikethrough')
662
+ if reverse:
663
+ tags.append('reverse')
664
+
665
+ self.text_area.insert(tk.END, str(text) + "\n", tuple(tags))
666
+ self.text_area.config(state=tk.DISABLED)
667
+ self.text_area.see(tk.END)
668
+ except tk.TclError as e:
669
+ if self.running:
670
+ self._safe_print(f"Tkinter error in display_colored: {e}")
671
+ except Exception as e:
672
+ if self.running:
673
+ self._safe_print(f"Error in display_colored: {e}")
674
+
675
+ if self.running:
676
+ self.root.after(0, _update)
656
677
 
657
678
  def _demo_colors(self) -> None:
658
679
  """Display ANSI color demo"""
659
680
  demo_texts = [
660
- ("\033[1mANSI Color Demo\033[0m\n", False), # Don't parse ANSI
661
- ("\033[1;37mBasic Colors:\033[0m\n", True),
662
- (" \033[30mBlack\033[0m \033[31mRed\033[0m \033[32mGreen\033[0m \033[33mYellow\033[0m\n", True),
663
- (" \033[34mBlue\033[0m \033[35mMagenta\033[0m \033[36mCyan\033[0m \033[37mWhite\033[0m\n", True),
664
- ("\n\033[1;37mBright Colors:\033[0m\n", True),
665
- (" \033[90mGray\033[0m \033[91mBright Red\033[0m \033[92mBright Green\033[0m\n", True),
666
- (" \033[93mBright Yellow\033[0m \033[94mBright Blue\033[0m \033[95mBright Magenta\033[0m\n", True),
667
- ("\n\033[1;37mBackground Colors:\033[0m\n", True),
668
- (" \033[40;37mBlack BG\033[0m \033[41mRed BG\033[0m \033[42mGreen BG\033[0m\n", True),
669
- (" \033[43mYellow BG\033[0m \033[44mBlue BG\033[0m \033[45mMagenta BG\033[0m\n", True),
670
- ("\n\033[1;37mExtended Colors:\033[0m\n", True),
671
- (" \033[38;5;1mDark Red\033[0m \033[38;5;9mRed\033[0m \033[38;5;10mGreen\033[0m \033[38;5;12mBlue\033[0m\n", True),
672
- (" \033[48;5;1mDark Red BG\033[0m \033[48;5;9mRed BG\033[0m\n", True),
673
- ("\n\033[1;37mTrue Colors (RGB):\033[0m\n", True),
674
- (" \033[38;2;255;0;0mRed\033[0m \033[38;2;0;255;0mGreen\033[0m \033[38;2;0;0;255mBlue\033[0m\n", True),
675
- ("\n\033[1;37mText Styles:\033[0m\n", True),
676
- (" \033[1mBold\033[0m \033[3mItalic\033[0m \033[4mUnderline\033[0m \033[9mStrikethrough\033[0m\n", True),
677
- ("\n\033[1;37mCombined Styles:\033[0m\n", True),
678
- (" \033[1;31mBold Red\033[0m \033[1;4;32mBold Underlined Green\033[0m\n", True),
679
- (" \033[1;33;44mBold Yellow on Blue\033[0m \033[1;37;41mBold White on Red\033[0m\n", True),
681
+ ("\x1b[1mANSI Color Demo\x1b[0m\n", False),
682
+ ("\x1b[1;37mBasic Colors:\x1b[0m\n", True),
683
+ (" \x1b[30mBlack\x1b[0m \x1b[31mRed\x1b[0m \x1b[32mGreen\x1b[0m \x1b[33mYellow\x1b[0m\n", True),
684
+ (" \x1b[34mBlue\x1b[0m \x1b[35mMagenta\x1b[0m \x1b[36mCyan\x1b[0m \x1b[37mWhite\x1b[0m\n", True),
685
+ ("\n\x1b[1;37mBright Colors:\x1b[0m\n", True),
686
+ (" \x1b[90mGray\x1b[0m \x1b[91mBright Red\x1b[0m \x1b[92mBright Green\x1b[0m\n", True),
687
+ (" \x1b[93mBright Yellow\x1b[0m \x1b[94mBright Blue\x1b[0m \x1b[95mBright Magenta\x1b[0m\n", True),
688
+ ("\n\x1b[1;37mBackground Colors:\x1b[0m\n", True),
689
+ (" \x1b[40;37mBlack BG\x1b[0m \x1b[41mRed BG\x1b[0m \x1b[42mGreen BG\x1b[0m\n", True),
690
+ (" \x1b[43mYellow BG\x1b[0m \x1b[44mBlue BG\x1b[0m \x1b[45mMagenta BG\x1b[0m\n", True),
691
+ ("\n\x1b[1;37mExtended Colors:\x1b[0m\n", True),
692
+ (" \x1b[38;5;1mDark Red\x1b[0m \x1b[38;5;9mRed\x1b[0m \x1b[38;5;10mGreen\x1b[0m \x1b[38;5;12mBlue\x1b[0m\n", True),
693
+ (" \x1b[48;5;1mDark Red BG\x1b[0m \x1b[48;5;9mRed BG\x1b[0m\n", True),
694
+ ("\n\x1b[1;37mTrue Colors (RGB):\x1b[0m\n", True),
695
+ (" \x1b[38;2;255;0;0mRed\x1b[0m \x1b[38;2;0;255;0mGreen\x1b[0m \x1b[38;2;0;0;255mBlue\x1b[0m\n", True),
696
+ ("\n\x1b[1;37mText Styles:\x1b[0m\n", True),
697
+ (" \x1b[1mBold\x1b[0m \x1b[3mItalic\x1b[0m \x1b[4mUnderline\x1b[0m \x1b[9mStrikethrough\x1b[0m\n", True),
698
+ ("\n\x1b[1;37mCombined Styles:\x1b[0m\n", True),
699
+ (" \x1b[1;31mBold Red\x1b[0m \x1b[1;4;32mBold Underlined Green\x1b[0m\n", True),
700
+ (" \x1b[1;33;44mBold Yellow on Blue\x1b[0m \x1b[1;37;41mBold White on Red\x1b[0m\n", True),
680
701
  ]
681
702
 
682
703
  for text, parse_ansi in demo_texts:
@@ -685,36 +706,46 @@ class Py2GUI:
685
706
  def set_theme(self, theme_name: str) -> None:
686
707
  """Set theme"""
687
708
  def _set_theme():
688
- if theme_name == "dark":
689
- self.text_area.config(bg="black", fg="white", insertbackground="white")
690
- self.text_area.tag_configure("default", foreground="white", background="black")
691
- elif theme_name == "light":
692
- self.text_area.config(bg="white", fg="black", insertbackground="black")
693
- self.text_area.tag_configure("default", foreground="black", background="white")
694
- elif theme_name == "matrix":
695
- self.text_area.config(bg="black", fg="#00ff00", insertbackground="#00ff00")
696
- self.text_area.tag_configure("default", foreground="#00ff00", background="black")
697
- else: # default
698
- self.text_area.config(bg="black", fg="white", insertbackground="white")
699
- self.text_area.tag_configure("default", foreground="white", background="black")
700
-
701
- self.text_area.see(tk.END)
709
+ try:
710
+ if theme_name == "dark":
711
+ self.text_area.config(bg="black", fg="white", insertbackground="white")
712
+ self.text_area.tag_configure("default", foreground="white", background="black")
713
+ elif theme_name == "light":
714
+ self.text_area.config(bg="white", fg="black", insertbackground="black")
715
+ self.text_area.tag_configure("default", foreground="black", background="white")
716
+ elif theme_name == "matrix":
717
+ self.text_area.config(bg="black", fg="#00ff00", insertbackground="#00ff00")
718
+ self.text_area.tag_configure("default", foreground="#00ff00", background="black")
719
+ else: # Default theme
720
+ self.text_area.config(bg="black", fg="white", insertbackground="white")
721
+ self.text_area.tag_configure("default", foreground="white", background="black")
722
+
723
+ self.text_area.see(tk.END)
724
+ except tk.TclError as e:
725
+ if self.running:
726
+ self._safe_print(f"Tkinter error setting theme: {e}")
702
727
 
703
- self.root.after(0, _set_theme)
728
+ if self.running:
729
+ self.root.after(0, _set_theme)
704
730
 
705
731
  def user_write(self, prompt: str = "Input:") -> Optional[str]:
706
- """Thread-safe input dialog (opens in a new window)"""
732
+ """Thread-safe input dialog (opens in new window)"""
707
733
  if not self.running:
708
734
  return None
735
+
709
736
  def _ask():
710
737
  try:
711
738
  result = simpledialog.askstring("Input", prompt, parent=self.root)
712
739
  self.input_queue.put(result)
713
740
  except tk.TclError:
714
741
  self.input_queue.put(None)
742
+ except Exception as e:
743
+ self._safe_print(f"Error in user_write dialog: {e}")
744
+ self.input_queue.put(None)
745
+
715
746
  self.root.after(0, _ask)
716
747
 
717
- # Wait for input with timeout to check if still running
748
+ # Wait for input, but check if still running
718
749
  while self.running:
719
750
  try:
720
751
  return self.input_queue.get(timeout=0.1)
@@ -723,40 +754,40 @@ class Py2GUI:
723
754
  return None
724
755
 
725
756
  def user_type_in(self, prompt: str = ">> ") -> Optional[str]:
726
- """
727
- Thread-safe terminal-style input (embedded in the main window)
728
- User types in the terminal-like input field and presses Enter to send
729
- """
757
+ """Thread-safe terminal-style input (embedded in main window)"""
730
758
  if not self.running:
731
759
  return None
732
760
 
733
761
  def _prepare_input():
734
762
  try:
735
- # Update the prompt in the label
763
+ # Update prompt in label
736
764
  self.input_label.config(text=prompt)
737
765
 
738
- # Clear any previous input
766
+ # Clear previous input
739
767
  self.input_var.set("")
740
768
 
741
- # Focus the input field
769
+ # Focus input field
742
770
  self.input_entry.focus_set()
743
771
 
744
- # Enable the input field
772
+ # Enable input field
745
773
  self.input_entry.config(state=tk.NORMAL)
746
- except tk.TclError:
747
- pass
774
+ except tk.TclError as e:
775
+ if self.running:
776
+ self._safe_print(f"Tkinter error preparing input: {e}")
748
777
 
749
- # Clear the queue in case there are stale values
778
+ # Clear old values from queue
750
779
  while not self.type_in_queue.empty():
751
780
  try:
752
781
  self.type_in_queue.get_nowait()
753
782
  except queue.Empty:
754
783
  break
755
784
 
756
- # Prepare the input field
785
+ # Prepare input field
757
786
  try:
758
787
  self.root.after(0, _prepare_input)
759
- except tk.TclError:
788
+ except tk.TclError as e:
789
+ if self.running:
790
+ self._safe_print(f"Tkinter error scheduling input preparation: {e}")
760
791
  return None
761
792
 
762
793
  # Block and wait for user input
@@ -768,68 +799,100 @@ class Py2GUI:
768
799
  return None
769
800
 
770
801
  def _on_enter_pressed(self, event: Optional[tk.Event] = None) -> str:
771
- """Handle Enter key press in the input field"""
802
+ """Handle Enter key press in input field"""
772
803
  self._on_send_input()
773
804
  return "break" # Prevent default behavior
774
805
 
775
806
  def _on_send_input(self) -> None:
776
- """Send the input from the terminal-style input field"""
777
- user_input = self.input_var.get().strip()
778
-
779
- if user_input:
780
- # Put the input in the queue
781
- self.type_in_queue.put(user_input)
782
-
783
- # Display the user input in the output area
784
- self.text_area.config(state=tk.NORMAL)
785
- self.text_area.insert(tk.END, f"{self.input_label.cget('text')}{user_input}\n", 'default')
786
- self.text_area.config(state=tk.DISABLED)
787
- self.text_area.see(tk.END)
807
+ """Send input from terminal-style input field"""
808
+ try:
809
+ user_input = self.input_var.get().strip()
788
810
 
789
- # Clear the input field
790
- self.input_var.set("")
811
+ if user_input:
812
+ # Put input in queue
813
+ self.type_in_queue.put(user_input)
814
+
815
+ # Display user input in output area
816
+ self.text_area.config(state=tk.NORMAL)
817
+ self.text_area.insert(tk.END, f"{self.input_label.cget('text')}{user_input}\n", 'default')
818
+ self.text_area.config(state=tk.DISABLED)
819
+ self.text_area.see(tk.END)
820
+
821
+ # Clear input field
822
+ self.input_var.set("")
823
+ except tk.TclError as e:
824
+ if self.running:
825
+ self._safe_print(f"Tkinter error sending input: {e}")
826
+ except Exception as e:
827
+ if self.running:
828
+ self._safe_print(f"Error sending input: {e}")
791
829
 
792
830
  def _clear_input(self) -> None:
793
- """Clear the terminal-style input field"""
794
- self.input_var.set("")
795
- self.input_entry.focus_set()
831
+ """Clear terminal-style input field"""
832
+ try:
833
+ self.input_var.set("")
834
+ self.input_entry.focus_set()
835
+ except tk.TclError as e:
836
+ if self.running:
837
+ self._safe_print(f"Tkinter error clearing input: {e}")
796
838
 
797
839
  def focus_input(self) -> None:
798
- """Set focus to the terminal-style input field"""
799
- self.input_entry.focus_set()
840
+ """Set focus to terminal-style input field"""
841
+ try:
842
+ self.input_entry.focus_set()
843
+ except tk.TclError as e:
844
+ if self.running:
845
+ self._safe_print(f"Tkinter error focusing input: {e}")
800
846
 
801
847
  def clear(self) -> None:
802
- self.text_area.config(state=tk.NORMAL)
803
- self.text_area.delete(1.0, tk.END)
804
- self.text_area.config(state=tk.DISABLED)
848
+ """Clear output area"""
849
+ try:
850
+ self.text_area.config(state=tk.NORMAL)
851
+ self.text_area.delete(1.0, tk.END)
852
+ self.text_area.config(state=tk.DISABLED)
853
+ except tk.TclError as e:
854
+ if self.running:
855
+ self._safe_print(f"Tkinter error clearing text: {e}")
805
856
 
806
857
  def copy_text(self) -> None:
858
+ """Copy selected text"""
807
859
  try:
808
860
  selected = self.text_area.selection_get()
809
861
  self.root.clipboard_clear()
810
862
  self.root.clipboard_append(selected)
811
863
  except tk.TclError:
812
- pass
864
+ pass # No text selected
865
+ except Exception as e:
866
+ if self.running:
867
+ self._safe_print(f"Error copying text: {e}")
813
868
 
814
869
  def select_all(self) -> None:
815
- self.text_area.config(state=tk.NORMAL)
816
- self.text_area.tag_add(tk.SEL, "1.0", tk.END)
817
- self.text_area.config(state=tk.DISABLED)
818
- self.text_area.mark_set(tk.INSERT, "1.0")
819
- self.text_area.see(tk.INSERT)
870
+ """Select all text"""
871
+ try:
872
+ self.text_area.config(state=tk.NORMAL)
873
+ self.text_area.tag_add(tk.SEL, "1.0", tk.END)
874
+ self.text_area.config(state=tk.DISABLED)
875
+ self.text_area.mark_set(tk.INSERT, "1.0")
876
+ self.text_area.see(tk.INSERT)
877
+ except tk.TclError as e:
878
+ if self.running:
879
+ self._safe_print(f"Tkinter error selecting all: {e}")
820
880
 
821
881
  def exit(self) -> None:
882
+ """Exit GUI"""
822
883
  self.running = False
823
884
  try:
824
885
  self.root.quit()
825
886
  self.root.destroy()
826
887
  except tk.TclError:
827
888
  pass # Already destroyed
889
+ except Exception as e:
890
+ self._safe_print(f"Error exiting GUI: {e}")
828
891
 
829
892
  def run(self, func: Optional[Callable] = None, *args, **kwargs) -> Any:
830
893
  """
831
- Run the GUI and optionally a worker function.
832
- All Tkinter operations stay on main thread; your logic runs in a thread.
894
+ Run GUI and optional worker function
895
+ Tkinter operations stay in main thread; logic runs in another thread
833
896
  """
834
897
  result_queue = queue.Queue()
835
898
 
@@ -841,13 +904,17 @@ class Py2GUI:
841
904
  except Exception as e:
842
905
  self.display(f"Error: {e}\n{traceback.format_exc()}")
843
906
  result_queue.put(e)
844
- threading.Thread(target=worker, daemon=True).start()
907
+ thread = threading.Thread(target=worker, daemon=True)
908
+ thread.start()
845
909
 
846
910
  try:
847
911
  self.root.mainloop()
848
912
  except KeyboardInterrupt:
849
913
  self.exit()
850
-
914
+ except Exception as e:
915
+ self._safe_print(f"Error in mainloop: {e}")
916
+ self.exit()
917
+
851
918
  if func:
852
919
  # Wait for function to complete, but make it interruptible
853
920
  while self.running:
@@ -859,17 +926,67 @@ class Py2GUI:
859
926
 
860
927
 
861
928
  # Global instance and helper functions
862
- _gui_instance = Py2GUI()
929
+ _gui_instance = None
930
+
931
+ def _get_instance() -> Py2GUI:
932
+ """Get or create global instance"""
933
+ global _gui_instance
934
+ if _gui_instance is None or not _gui_instance.running:
935
+ _gui_instance = Py2GUI()
936
+ return _gui_instance
937
+
938
+ def display(text: str, parse_ansi: bool = True, font_family: Optional[str] = None,
939
+ font_size: Optional[int] = None, font_style: Optional[str] = None) -> None:
940
+ """Display text (auto newline)"""
941
+ _get_instance().display(text, parse_ansi, font_family, font_size, font_style)
942
+
943
+ def display_colored(text: str, fg_color: Optional[str] = None, bg_color: Optional[str] = None,
944
+ bold: bool = False, underline: bool = False, italic: bool = False,
945
+ strikethrough: bool = False, reverse: bool = False,
946
+ font_family: Optional[str] = None, font_size: Optional[int] = None,
947
+ font_style: Optional[str] = None) -> None:
948
+ """Directly display colored text"""
949
+ _get_instance().display_colored(text, fg_color, bg_color, bold, underline,
950
+ italic, strikethrough, reverse, font_family,
951
+ font_size, font_style)
952
+
953
+ def display_paragraph(text: str, parse_ansi: bool = True, font_family: Optional[str] = None,
954
+ font_size: Optional[int] = None, font_style: Optional[str] = None) -> None:
955
+ """Display paragraph (no auto newline)"""
956
+ _get_instance().display_paragraph(text, parse_ansi, font_family, font_size, font_style)
957
+
958
+ def user_write(prompt: str = "Input:") -> Optional[str]:
959
+ """User input (dialog)"""
960
+ return _get_instance().user_write(prompt)
961
+
962
+ def user_type_in(prompt: str = ">> ") -> Optional[str]:
963
+ """User input (terminal-style)"""
964
+ return _get_instance().user_type_in(prompt)
965
+
966
+ def clear() -> None:
967
+ """Clear output"""
968
+ _get_instance().clear()
969
+
970
+ def copy_text() -> None:
971
+ """Copy text"""
972
+ _get_instance().copy_text()
973
+
974
+ def select_all() -> None:
975
+ """Select all"""
976
+ _get_instance().select_all()
977
+
978
+ def exit_gui() -> None:
979
+ """Exit GUI"""
980
+ _get_instance().exit()
981
+
982
+ def run(func: Optional[Callable] = None, *args, **kwargs) -> Any:
983
+ """Run GUI"""
984
+ return _get_instance().run(func, *args, **kwargs)
985
+
986
+ def focus_input() -> None:
987
+ """Focus on input field"""
988
+ _get_instance().focus_input()
863
989
 
864
- display = _gui_instance.display
865
- display_colored = _gui_instance.display_colored
866
- display_paragraph = _gui_instance.display_paragraph
867
- user_write = _gui_instance.user_write
868
- user_type_in = _gui_instance.user_type_in
869
- clear = _gui_instance.clear
870
- copy_text = _gui_instance.copy_text
871
- select_all = _gui_instance.select_all
872
- exit_gui = _gui_instance.exit
873
- run = _gui_instance.run
874
- focus_input = _gui_instance.focus_input
875
- set_theme = _gui_instance.set_theme
990
+ def set_theme(theme_name: str) -> None:
991
+ """Set theme"""
992
+ _get_instance().set_theme(theme_name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py2gui
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Simple python to GUI (Tkinter) tool
5
5
  Author: Andy
6
6
  Requires-Python: >=3.8
@@ -0,0 +1,8 @@
1
+ py2gui/game_example.py,sha256=jl_Ko5_UcYxjEpDpWrf-K8mYERgXII431xlhIaOvvrA,26666
2
+ py2gui/py2gui.py,sha256=sJmGoRCfI-N_64eTplvuyXEzduB-B6A4uGtBZXxmd2E,42964
3
+ py2gui/test.py,sha256=eWdoQUIKF_VVyaw6fckkN6jZu5-D8IkcqBQzDkBlKXM,4353
4
+ py2gui-0.1.1.dist-info/licenses/LICENSE,sha256=9SfUknNU1t6kFscrCqKPf3vPbcxbyq-jPtX6J_mpgoo,1065
5
+ py2gui-0.1.1.dist-info/METADATA,sha256=Xyuo7js8HnqhLyD88_jsi57J5-97jX2igsMK-tHEuaM,215
6
+ py2gui-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
7
+ py2gui-0.1.1.dist-info/top_level.txt,sha256=NFgf8RRbdgAbdKXmuKobDL9dUeIc-911JuRuzzbR-7w,7
8
+ py2gui-0.1.1.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- py2gui/game_example.py,sha256=jl_Ko5_UcYxjEpDpWrf-K8mYERgXII431xlhIaOvvrA,26666
2
- py2gui/py2gui.py,sha256=wtGvNzCPccDFXI_9YpXfhuxF7NB1TcCt3Qc4naZmNAQ,38182
3
- py2gui/test.py,sha256=eWdoQUIKF_VVyaw6fckkN6jZu5-D8IkcqBQzDkBlKXM,4353
4
- py2gui-0.1.0.dist-info/licenses/LICENSE,sha256=9SfUknNU1t6kFscrCqKPf3vPbcxbyq-jPtX6J_mpgoo,1065
5
- py2gui-0.1.0.dist-info/METADATA,sha256=egNc7muDEgGXQyyqX6K5_lOEGcyZy-NsaTWAgsJR9gw,215
6
- py2gui-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
7
- py2gui-0.1.0.dist-info/top_level.txt,sha256=NFgf8RRbdgAbdKXmuKobDL9dUeIc-911JuRuzzbR-7w,7
8
- py2gui-0.1.0.dist-info/RECORD,,
File without changes