Added a rudimentary console for easier interaction with Pesterchum's code. Still WIP.
This commit is contained in:
parent
0b36b45ddb
commit
dabd2b46c1
2 changed files with 353 additions and 1 deletions
352
console.py
Normal file
352
console.py
Normal file
|
@ -0,0 +1,352 @@
|
|||
# vim: set autoindent ts=4 softtabstop=4 shiftwidth=4 textwidth=79 expandtab:
|
||||
# -*- coding=UTF-8; tab-width: 4 -*-
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
import re, os, traceback, sys
|
||||
import time, datetime
|
||||
from os import remove
|
||||
|
||||
import dataobjs, generic, memos, parsetools, ostools
|
||||
from version import _pcVersion
|
||||
|
||||
_datadir = ostools.getDataDir()
|
||||
|
||||
import logging
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
|
||||
|
||||
|
||||
class ConsoleWindow(QtGui.QDialog):
|
||||
# A simple console class, cobbled together from the corpse of another.
|
||||
|
||||
textArea = None
|
||||
textInput = None
|
||||
history = None
|
||||
# I should probably put up constants for 'direction' if this is going to
|
||||
# get this complicated. TODO!
|
||||
incoming_prefix = "<<<"
|
||||
outgoing_prefix = ">>>"
|
||||
neutral_prefix = "!!!"
|
||||
waiting_prefix = "..."
|
||||
|
||||
def __init__(self, parent):
|
||||
super(ConsoleWindow, self).__init__(parent)
|
||||
self.prnt = parent
|
||||
try:
|
||||
self.mainwindow = parent.mainwindow
|
||||
except:
|
||||
self.mainwindow = parent
|
||||
theme = self.mainwindow.theme
|
||||
|
||||
# Set up our style/window specifics
|
||||
self.setStyleSheet(theme["main/defaultwindow/style"])
|
||||
self.setWindowTitle("==> Console")
|
||||
self.resize(350,300)
|
||||
|
||||
self.textArea = ConsoleText(theme, self)
|
||||
self.textInput = ConsoleInput(theme, self)
|
||||
self.textInput.setFocus()
|
||||
|
||||
self.connect(self.textInput, QtCore.SIGNAL('returnPressed()'),
|
||||
self, QtCore.SLOT('sentMessage()'))
|
||||
|
||||
self.chumopen = True
|
||||
self.chum = self.mainwindow.profile()
|
||||
self.history = dataobjs.PesterHistory()
|
||||
|
||||
# For backing these up
|
||||
self.stdout = self.stderr = None
|
||||
|
||||
layout_0 = QtGui.QVBoxLayout()
|
||||
layout_0.addWidget(self.textArea)
|
||||
layout_0.addWidget(self.textInput)
|
||||
self.setLayout(layout_0)
|
||||
|
||||
def parent(self):
|
||||
return self.prnt
|
||||
|
||||
def clearNewMessage(self):
|
||||
pass
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def sentMessage(self):
|
||||
text = self.textInput.text()
|
||||
# TODO: Make this deal with unicode text, it'll crash and burn as-is.
|
||||
text = str(text)
|
||||
text = text.rstrip()
|
||||
|
||||
self.history.add(text)
|
||||
self.textInput.setText("")
|
||||
|
||||
self.execInConsole(text)
|
||||
# Scroll down to the bottom so we can see the results.
|
||||
sb = self.textArea.verticalScrollBar()
|
||||
sb.setValue(sb.maximum())
|
||||
|
||||
def addTraceback(self, tb=None):
|
||||
# We should do the formatting here, but eventually pass it to textArea
|
||||
# to addMessage whatever output we produced.
|
||||
# If we're called by addMessage - and we should be - then sys.stdout is
|
||||
# still being redirected into the console.
|
||||
# TODO: Just make an object for setting contexts (and thus optionally
|
||||
# redirecting prints). Use 'with', of course.
|
||||
# TODO: Make this exclude *our* processing from the traceback stack.
|
||||
try:
|
||||
self.addMessage(traceback.format_exc(), direction=0)
|
||||
except Exception as err:
|
||||
logging.error("Failed to display error message (???): %s" % err)
|
||||
|
||||
def addMessage(self, msg, direction):
|
||||
# Redirect to where these things belong.
|
||||
self.textArea.addMessage(msg, direction=direction)
|
||||
|
||||
def closeEvent(self, event):
|
||||
# TODO: Set up ESC to close the console...or refer to hiding it as
|
||||
# closing it. Not sure which is preferable.
|
||||
parent = self.parent()
|
||||
parent.console.is_open = False
|
||||
parent.console.window = None
|
||||
|
||||
# Actual console stuff.
|
||||
def execInConsole(self, scriptstr, env=None):
|
||||
# Since that's what imports *us*, this should be okay
|
||||
# Tab completion could be set up in ConsoleInput, and would be nice
|
||||
import pesterchum as pchum
|
||||
|
||||
if env is None:
|
||||
env = pchum._retrieveGlobals()
|
||||
|
||||
# Modify the environment the script will execute in.
|
||||
# NOTE: This doesn't presently work, for some reason.
|
||||
_CUSTOM_ENV = {
|
||||
"CONSOLE": self,
|
||||
"MAINWIN": self.mainwindow,
|
||||
"PCONFIG": self.mainwindow.config
|
||||
}
|
||||
for k in _CUSTOM_ENV:
|
||||
if k not in env:
|
||||
# Don't overwrite anything!
|
||||
env[k] = _CUSTOM_ENV[k]
|
||||
else:
|
||||
warn = "Console environment item {!r} already exists in main."
|
||||
warn.format(k)
|
||||
logging.warning(warn)
|
||||
|
||||
# Display the input we provided
|
||||
self.addMessage(scriptstr, 1)
|
||||
# Replace the old writer (for now)
|
||||
sysout, sys.stdout = sys.stdout, self
|
||||
try:
|
||||
code = compile(scriptstr + '\n', "<string>", "single")
|
||||
result = eval(code, env)
|
||||
except:
|
||||
# Something went wrong.
|
||||
#~logging.exception("Exception: %s", err, exc_info=sys.exc_info())
|
||||
self.addTraceback(sys.exc_info()[2])
|
||||
else:
|
||||
# No errors.
|
||||
if result is not None:
|
||||
#~self.addMessage(repr(result), -1)
|
||||
print repr(result)
|
||||
finally:
|
||||
# Restore system output.
|
||||
sys.stdout = sysout
|
||||
|
||||
# Try to clean us out of globals - this might be disabled
|
||||
# later.
|
||||
for k in _CUSTOM_ENV:
|
||||
# Remove the key we added.
|
||||
env.pop(k, None)
|
||||
|
||||
def write(self, data):
|
||||
# Replaces sys.stdout briefly
|
||||
# We only ever use this for receiving, so it's safe to assume the
|
||||
# direction is always -1.
|
||||
if not isinstance(data, list):
|
||||
data = data.split('\n')
|
||||
for line in data:
|
||||
if len(line):
|
||||
self.addMessage(line, -1)
|
||||
|
||||
|
||||
class ConsoleText(QtGui.QTextEdit):
|
||||
def __init__(self, theme, parent=None):
|
||||
super(ConsoleText, self).__init__(parent)
|
||||
if hasattr(self.parent(), 'mainwindow'):
|
||||
self.mainwindow = self.parent().mainwindow
|
||||
else:
|
||||
self.mainwindow = self.parent()
|
||||
|
||||
self.hasTabs = False
|
||||
self.initTheme(theme)
|
||||
self.setReadOnly(True)
|
||||
self.setMouseTracking(True)
|
||||
self.textSelected = False
|
||||
|
||||
self.connect(self, QtCore.SIGNAL('copyAvailable(bool)'),
|
||||
self, QtCore.SLOT('textReady(bool)'))
|
||||
self.urls = {}
|
||||
|
||||
# Stripped out animation init - it's all cruft to us.
|
||||
|
||||
@QtCore.pyqtSlot(bool)
|
||||
def textReady(self, ready):
|
||||
self.textSelected = ready
|
||||
|
||||
def initTheme(self, theme):
|
||||
if theme.has_key("convo/scrollbar"):
|
||||
self.setStyleSheet("QTextEdit { %s } QScrollBar:vertical { %s } QScrollBar::handle:vertical { %s } QScrollBar::add-line:vertical { %s } QScrollBar::sub-line:vertical { %s } QScrollBar:up-arrow:vertical { %s } QScrollBar:down-arrow:vertical { %s }" % (theme["convo/textarea/style"], theme["convo/scrollbar/style"], theme["convo/scrollbar/handle"], theme["convo/scrollbar/downarrow"], theme["convo/scrollbar/uparrow"], theme["convo/scrollbar/uarrowstyle"], theme["convo/scrollbar/darrowstyle"] ))
|
||||
else:
|
||||
self.setStyleSheet("QTextEdit { %s }" % (theme["convo/textarea/style"]))
|
||||
|
||||
def addMessage(self, msg, direction):
|
||||
# Display a message we've received.
|
||||
# Direction > 0 == out (sent by us); < 0 == in (sent by script).
|
||||
if len(msg) == 0:
|
||||
return
|
||||
#~color = chum.colorcmd()
|
||||
#~initials = chum.initials()
|
||||
parent = self.parent()
|
||||
mwindow = parent.mainwindow
|
||||
|
||||
systemColor = QtGui.QColor(mwindow.theme["convo/systemMsgColor"])
|
||||
|
||||
if mwindow.config.showTimeStamps():
|
||||
if mwindow.config.time12Format():
|
||||
timestamp = time.strftime("[%I:%M")
|
||||
else:
|
||||
timestamp = time.strftime("[%H:%M")
|
||||
if mwindow.config.showSeconds():
|
||||
timestamp += time.strftime(":%S] ")
|
||||
else:
|
||||
timestamp += "] "
|
||||
else:
|
||||
timestamp = ""
|
||||
|
||||
# Figure out what prefix to use.
|
||||
if direction > 0:
|
||||
# Outgoing.
|
||||
prefix = parent.outgoing_prefix
|
||||
elif direction < 0:
|
||||
# Incoming.
|
||||
prefix = parent.incoming_prefix
|
||||
elif direction == 0:
|
||||
# We could just 'else' here, but there might be some oddness later.
|
||||
prefix = parent.neutral_prefix
|
||||
|
||||
# Later, this will have to escape things so we don't parse them,
|
||||
# likely...hm.
|
||||
#~result = "<span style=\"color:#000000\">{} {} {!r}</span>"
|
||||
# The input we get is already repr'd...we pass it via print, and thus
|
||||
# do that there.
|
||||
result = "{}{} {}\n"
|
||||
result = result.format(timestamp, prefix, msg)
|
||||
#~self.append(result)
|
||||
self.insertPlainText(result)
|
||||
|
||||
# Direction doesn't matter here - it's the console.
|
||||
self.lastmsg = datetime.datetime.now()
|
||||
# This needs to finish being rewritten....
|
||||
|
||||
def changeTheme(self, theme):
|
||||
self.initTheme(theme)
|
||||
sb = self.verticalScrollBar()
|
||||
sb.setValue(sb.maximum())
|
||||
|
||||
def focusInEvent(self, event):
|
||||
self.parent().clearNewMessage()
|
||||
super(ConsoleText, self).focusInEvent(event)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
# NOTE: This doesn't give focus to the input bar, which it probably
|
||||
# should.
|
||||
# karxi: Test for tab changing?
|
||||
if hasattr(self.parent(), 'textInput'):
|
||||
if event.key() not in (QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown,
|
||||
QtCore.Qt.Key_Up, QtCore.Qt.Key_Down):
|
||||
self.parent().textInput.keyPressEvent(event)
|
||||
|
||||
super(ConsoleText, self).keyPressEvent(event)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
url = self.anchorAt(event.pos())
|
||||
if url != "":
|
||||
# Skip memo/handle recognition
|
||||
# NOTE: Ctrl+Click copies the URL. Maybe it should select it?
|
||||
if event.modifiers() == QtCore.Qt.ControlModifier:
|
||||
QtGui.QApplication.clipboard().setText(url)
|
||||
else:
|
||||
# This'll probably be removed. May change the lexer out.
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtCore.QUrl.TolerantMode))
|
||||
|
||||
super(ConsoleText, self).mousePressEvent(event)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
# Change our cursor when we roll over links (anchors).
|
||||
super(ConsoleText, self).mouseMoveEvent(event)
|
||||
if self.anchorAt(event.pos()):
|
||||
if self.viewport().cursor().shape != QtCore.Qt.PointingHandCursor:
|
||||
self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
|
||||
else:
|
||||
self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
# This is almost certainly no longer necessary.
|
||||
textMenu = self.createStandardContextMenu()
|
||||
#~if self.textSelected:
|
||||
#~ self.submitLogAction = QtGui.QAction("Submit to Pesterchum QDB", self)
|
||||
#~ self.connect(self.submitLogAction, QtCore.SIGNAL('triggered()'),
|
||||
#~ self, QtCore.SLOT('submitLog()'))
|
||||
#~ textMenu.addAction(self.submitLogAction)
|
||||
textMenu.exec_(event.globalPos())
|
||||
|
||||
|
||||
class ConsoleInput(QtGui.QLineEdit):
|
||||
"""The actual text entry box on a ConsoleWindow."""
|
||||
# I honestly feel like this could just be made a private class of
|
||||
# ConsoleWindow, but...best not to overcomplicate things.
|
||||
stylesheet_path = "convo/input/style"
|
||||
|
||||
def __init__(self, theme, parent=None):
|
||||
super(ConsoleInput, self).__init__(parent)
|
||||
|
||||
self.changeTheme(theme)
|
||||
|
||||
def changeTheme(self, theme):
|
||||
self.setStyleSheet(theme[self.stylesheet_path])
|
||||
|
||||
def focusInEvent(self, event):
|
||||
# We gained focus. Notify the parent window that this happened.
|
||||
self.parent().clearNewMessage()
|
||||
self.parent().textArea.textCursor().clearSelection()
|
||||
|
||||
super(ConsoleInput, self).focusInEvent(event)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
evtkey = event.key()
|
||||
parent = self.parent()
|
||||
|
||||
# If a key is pressed here, we're not idle....
|
||||
# NOTE: Do we really want everyone knowing we're around if we're
|
||||
# messing around in the console? Hm.
|
||||
parent.mainwindow.idler.time = 0
|
||||
|
||||
if evtkey == QtCore.Qt.Key_Up:
|
||||
text = unicode(self.text())
|
||||
next = parent.history.next(text)
|
||||
if next is not None:
|
||||
self.setText(next)
|
||||
elif evtkey == QtCore.Qt.Key_Down:
|
||||
prev = parent.history.prev()
|
||||
if prev is not None:
|
||||
self.setText(prev)
|
||||
elif evtkey in (QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown):
|
||||
parent.textArea.keyPressEvent(event)
|
||||
else:
|
||||
super(ConsoleInput, self).keyPressEvent(event)
|
||||
|
||||
|
||||
|
||||
|
2
convo.py
2
convo.py
|
@ -542,7 +542,7 @@ class PesterInput(QtGui.QLineEdit):
|
|||
|
||||
class PesterConvo(QtGui.QFrame):
|
||||
def __init__(self, chum, initiated, mainwindow, parent=None):
|
||||
QtGui.QFrame.__init__(self, parent)
|
||||
super(PesterConvo, self).__init__(parent)
|
||||
self.setAttribute(QtCore.Qt.WA_QuitOnClose, False)
|
||||
self.setObjectName(chum.handle)
|
||||
self.setFocusPolicy(QtCore.Qt.ClickFocus)
|
||||
|
|
Loading…
Reference in a new issue