Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bf4d072
If an error happens processing excepthook/console write it to __stderr__
MHendricks Oct 31, 2025
1fac88d
Standardize the name for old streams across FileLogger and Console
MHendricks Nov 20, 2025
f41aea8
Convert uses of `self.window()` to `self.console` property for console
MHendricks Oct 30, 2025
7cd82d9
Add support for QtDesigner using PyQt
MHendricks Nov 20, 2025
e080da2
Split console into multiple classes to allow for output only widgets
MHendricks Nov 25, 2025
9726202
Convert to StreamTypes enum instead of module level int values
MHendricks Nov 3, 2025
34b151e
Control if streams are installed for a given Console instance
MHendricks Nov 19, 2025
cd7acf7
Add ability to customize the right click menu of Consoles
MHendricks Nov 24, 2025
f58ec0c
LoggerWindowHandler can now force the console to print
MHendricks Nov 3, 2025
f2d042d
Move QtPropertyInit from preditor.gui to preditor.cute
MHendricks Nov 3, 2025
91c3689
QtPropertyInit can now have list, dict, set used for mutable defaults
MHendricks Nov 4, 2025
2154e58
Re-work truncation code and move console result output into Console
MHendricks Nov 17, 2025
a01921d
ConsoleBase now displays logging information using handlers
MHendricks Dec 4, 2025
de1a1ee
ConsoleBase now can enable traceback display without using stderr
MHendricks Nov 11, 2025
fd3f25a
Simplify Preferences layout
MHendricks Nov 19, 2025
6c740e1
ConsoleBase and Console now handle not having a controller
MHendricks Nov 20, 2025
f88321e
Move the code to raise and show the LoggerWindow into the class instance
MHendricks Nov 20, 2025
01d0f25
Create an example of the various features of ConsoleBase
MHendricks Dec 4, 2025
1ed8cd0
Add `@preditor.utils.call_stack.log_calls` decorator
MHendricks Nov 20, 2025
b450000
Right click option to insert <hr> for visual separation in console
MHendricks Nov 20, 2025
b9f50ff
Limit link cursor override to console not entire app
MHendricks Nov 26, 2025
8cc7da1
Rework text file encoding detection
MHendricks Nov 26, 2025
b7a0a50
Move GroupTabWidget into its own file to prevent circular imports
MHendricks Nov 27, 2025
687d50d
Prevent duplicate offending-line-output for console syntax errors
macevhicz Dec 2, 2025
3205ed0
Add 2.0 Change Log
MHendricks Dec 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ blocks([...](https://docs.python.org/3/glossary.html#term-...)), use the workbox
logging levels.
* You can install logging handlers that have had PrEditor plugins written for them.
* Known python logger levels are saved and restored.
* **OutputConsole:** Selectively shows output from stdout, stderr, specific python
loggers, and tracebacks(not using stderr). This can be used in various widgets to
show selected output. See [examples/output_console.py](examples/output_console.py)
for an example of the various modes.
* All code is run in `__main__`. In code you can add objects to it for inspection in PrEditor.
* `Ctrl + Shift + PgUp/PgDown` changes focus between the console and workbox.
* `Ctrl + Alt + Shift + PgUp/PgDown` changes focus and copies the current prompt
Expand Down Expand Up @@ -211,3 +215,46 @@ or add menu items. When using this plugin, make sure to use the
* `preditor.plug.logging_handlers`: Used to add custom python logging handlers
to the LoggingLevelButton's handlers sub-menus. This allows you to install a
handler instance on a specific logging object.

# Qt Designer integration

PrEditor includes some reusable widgets that are useful to integrate into your
own custom interfaces. These can be directly imported and added but PrEditor also
defines Qt Designer plugins so you can directly add them to your designer files.

Note: This has currently only been tested using [PyQt5](https://pypi.org/project/pyqt5-tools/)/[PyQt6](https://pypi.org/project/pyqt6-tools/).

To load the plugins append the path to [preditor/gui/qtdesigner](/preditor/gui/qtdesigner)
to the `PYQTDESIGNERPATH` environment variable.

# Change Log

* **2.0.0:** OutputConsole, link files to workboxe tabs and Workbox editing history
* New reusable `OutputConsole` widget that optionally shows stdout, stderr, logging messages, and tracebacks. See the [example](/examples/output_console.py) implementation for details.
* Workbox tabs can be linked to files and edited externally
* Workbox content change history is versioned and you can quickly switch between the versions.
* Recently closed workbox tabs can be be re-opened
* Workbox preference saving has been re-worked. It will automatically migrate
to the new setup so you won't loose your current workbox tab contents. However
after upgrading if you want to switch back to the v1.X release see details below.

<details>

In the rare case that you must revert to older Preditor (v1.X), you will only
see the workboxes you had when you updated to PrEditor v2.0. When you switch
back to v2.0 again, you still may only see those same workboxes. This can be
fixed with these steps, which in summary is to replace `preditor_pref.json`
with one of the backups of that file.

* Options Menu > Preferences
* In the `Prefs files on disk` section, click Browse. An Explorer window opens.
* Close PrEditor
* Go into the `prefs_bak` folder
* Sort by name or by date, so most recent files are at the top
* Look for a backup that is at least slightly larger than recent ones. If they are all the same size, go with the latest one.
* Copy that into the parent directory (ie PrEditor)
* Remove `preditor_pref.json`
* Rename the `preditor_pref<timestamp>.json` file you copied, so it is `preditor_pref.json`
* Restart PrEditor. Check if it has all your workboxes.
* If it still isn't correct, do a little sleuthing or trial and error to find the correct backup to use.
</details>
162 changes: 162 additions & 0 deletions examples/output_console.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"""Example of using `preditor.gui.output_console.OutputConsole` in a UI.

`python output_console.py`
"""


import logging
import sys

import Qt
from Qt.QtCore import QDateTime
from Qt.QtWidgets import QApplication, QMainWindow

import preditor

logger_a = logging.getLogger("logger_a")
logger_a_child = logging.getLogger("logger_a.child")
logger_b = logging.getLogger("logger_b")
logger_c = logging.getLogger("logger_c")


class ExampleApp(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent=parent)
# Use a .ui file to simplify the example code setup.
Qt.QtCompat.loadUi(__file__.replace(".py", ".ui"), self)

# Connect signals to test buttons
self.uiClearBTN.released.connect(self.clear_all)
self.uiAllLoggingDebugBTN.released.connect(self.all_logging_level_debug)
self.uiAllLoggingWarningBTN.released.connect(self.all_logging_level_warning)
self.uiLoggingCriticalBTN.released.connect(self.level_critical)
self.uiLoggingErrorBTN.released.connect(self.level_error)
self.uiLoggingWarningBTN.released.connect(self.level_warning)
self.uiLoggingInfoBTN.released.connect(self.level_info)
self.uiLoggingDebugBTN.released.connect(self.level_debug)
self.uiErrorBTN.released.connect(self.raise_exception)
self.uiPrintTimeBTN.released.connect(self.print_time)
self.uiPrintTimeErrBTN.released.connect(self.print_time_stderr)
self.uiSendLoggingBTN.released.connect(self.send_logging)

# 1. Create the preditor instance and connect to the console's controllers
plog = preditor.instance(parent=self, create=True)
preditor.connect_preditor(self)
self.uiAllLog.controller = plog
self.uiSelectLog.controller = plog
self.uiStdout.controller = plog
self.uiStderr.controller = plog

# 2. Configure the various OutputConsole widgets.
# Note: this can be done in the .ui file, but for this example we will
# configure it in code.

# See this method for how to configure uiAllLog to show all logging
# messages of all levels
self.all_logging_level_warning()

# Configure uiSelectLog to show logging messages from specific handlers
self.uiSelectLog.logging_handlers = [
(
"logger_a,DEBUG,fmt=[%(levelname)s %(module)s.%(funcName)s "
"line:%(lineno)d] %(message)s"
),
"logger_b,WARNING",
]
# And show tracebacks without showing all stderr text
self.uiSelectLog.stream_echo_tracebacks = True

# Configure uiStdout to only show stdout text
self.uiStdout.stream_echo_stdout = True

# Configure uiStderr to only show stderr text
self.uiStderr.stream_echo_stderr = True

def all_logging_level_debug(self):
"""Update this widget to show up to debug messages for all loggers.
Hide the PyQt loggers as they just clutter the output for this demo.
"""
self.uiAllLog.logging_handlers = [
"root,level=DEBUG",
"PyQt5,level=CRITICAL",
"PyQt6,level=CRITICAL",
]

def all_logging_level_warning(self):
"""Update this widget to show up to warning messages for all loggers.
Hide the PyQt loggers as they just clutter the output for this demo.
"""
self.uiAllLog.logging_handlers = [
"root,level=WARNING",
# Suppress PyQt logging messages like the .ui file parsing
# logging messages created when first showing the PrEditor instance.
"PyQt5,level=CRITICAL",
"PyQt6,level=CRITICAL",
]

def clear_all(self):
"""Clear the text from all consoles"""
self.uiAllLog.clear()
self.uiSelectLog.clear()
self.uiStdout.clear()
self.uiStderr.clear()

def level_critical(self):
logging.root.setLevel(logging.CRITICAL)

def level_error(self):
logging.root.setLevel(logging.ERROR)

def level_warning(self):
logging.root.setLevel(logging.WARNING)

def level_info(self):
logging.root.setLevel(logging.INFO)

def level_debug(self):
logging.root.setLevel(logging.DEBUG)

def message_time(self):
return f"The time is: {QDateTime.currentDateTime().toString()}"

def print_time(self):
print(self.message_time())

def print_time_stderr(self):
print(self.message_time(), file=sys.stderr)

def raise_exception(self):
raise RuntimeError(self.message_time())

def send_logging(self):
logger_a.critical("A critical msg for logger_a")
logger_a.error("A error msg for logger_a")
logger_a.warning("A warning msg for logger_a")
logger_a.info("A info msg for logger_a")
logger_a.debug("A debug msg for logger_a")
logger_a_child.warning("A warning msg for logger_a.child")
logger_a_child.debug("A debug msg for logger_a.child")
logger_b.warning("A warning msg for logger_b")
logger_b.debug("A debug msg for logger_b")
logger_c.warning("A warning msg for logger_c")
logger_c.debug("A debug msg for logger_c")
logging.root.warning("A warning msg for logging.root")
logging.root.debug("A debug msg for logging.root")


if __name__ == '__main__':
# Configure PrEditor for this application, start capturing all text output
# from stderr/stdout so once PrEditor is launched, it can show this text.
# This does not initialize any QtGui/QtWidgets.
preditor.configure(
# This is the name used to store PrEditor preferences and workboxes
# specific to this application.
'output_console',
)

# Create a Gui Application allowing the user to show PrEditor
app = QApplication(sys.argv)
main_gui = ExampleApp()

main_gui.show()
app.exec_()
Loading