Make Ctrl+Alt+w pipe widget information to console
The key combination used for this is likely to change. See ConsoleWindow.designateCurrentWidget() for precisely what this does and means. Essentially, mousing over something (with the console open) and hitting that combination of keys will give information on the GUI element the user had the mouse over at the time (if any), via the console. It also tells you what the stylesheet looks like, if it has one; if it doesn't have one, it looks for one on the object's parents, posting that instead if applicable. The last selected widget is stored in CONSOLE.selected_widget - it can be modified directly from there. More features and detailed information may come in the future.
This commit is contained in:
parent
cf37cb6c36
commit
195b59f29e
3 changed files with 173 additions and 28 deletions
|
@ -161,8 +161,11 @@ version of the client. There aren't really any other options.
|
|||
* Stop us from sending IDLE messages to NickServ
|
||||
* Fix NickServ auto-login things
|
||||
* Make a window that can be used to interface with the script directly - a
|
||||
simple Python console.
|
||||
simple Python console
|
||||
* Make the memo name entry box accept a comma-separated list
|
||||
* Make console users able to check widget theme/style information via mouseover
|
||||
and key combo (for people making themes or debugging)
|
||||
* This is presently Ctrl+Alt+w, after opening the console (Ctrl+~).
|
||||
|
||||
### Backend
|
||||
* Perpetual code cleanup, refactoring, simplification and general improvements
|
||||
|
|
133
console.py
133
console.py
|
@ -1,4 +1,5 @@
|
|||
# -*- coding=UTF-8; tab-width: 4 -*-
|
||||
from __future__ import print_function
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
import re, os, traceback, sys
|
||||
|
@ -29,10 +30,14 @@ class ConsoleWindow(QtGui.QDialog):
|
|||
# I should probably put up constants for 'direction' if this is going to
|
||||
# get this complicated. TODO!
|
||||
incoming_prefix = "<<<"
|
||||
miscinfo_prefix = "==>"
|
||||
outgoing_prefix = ">>>"
|
||||
neutral_prefix = "!!!"
|
||||
waiting_prefix = "..."
|
||||
|
||||
selected_widget = None
|
||||
show_info_on_select = True
|
||||
|
||||
_CUSTOM_ENV = {}
|
||||
|
||||
def __init__(self, parent):
|
||||
|
@ -54,8 +59,6 @@ class ConsoleWindow(QtGui.QDialog):
|
|||
self.connect(self.text.input, QtCore.SIGNAL('returnPressed()'),
|
||||
self, QtCore.SLOT('sentMessage()'))
|
||||
|
||||
self.chumopen = True
|
||||
self.chum = self.mainwindow.profile()
|
||||
self.text.history = dataobjs.PesterHistory()
|
||||
|
||||
# For backing these up
|
||||
|
@ -110,6 +113,7 @@ class ConsoleWindow(QtGui.QDialog):
|
|||
parent = self.parent()
|
||||
parent.console.is_open = False
|
||||
parent.console.window = None
|
||||
return super(ConsoleWindow, self).closeEvent(event)
|
||||
|
||||
def hideEvent(self, event):
|
||||
parent = self.parent()
|
||||
|
@ -118,7 +122,7 @@ class ConsoleWindow(QtGui.QDialog):
|
|||
def initTheme(self, theme):
|
||||
# Set up our style/window specifics
|
||||
self.changeTheme(theme)
|
||||
self.resize(350,300)
|
||||
self.resize(400,600)
|
||||
|
||||
def changeTheme(self, theme):
|
||||
self.setStyleSheet(theme[self.stylesheet_path])
|
||||
|
@ -127,6 +131,103 @@ class ConsoleWindow(QtGui.QDialog):
|
|||
self.text.area.changeTheme(theme)
|
||||
self.text.input.changeTheme(theme)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def designateCurrentWidget(self):
|
||||
# Display and save the current widget!
|
||||
# TODO: Consider (reversible) highlighting or selection or something
|
||||
# fancy. It'd help people write styles, wouldn't it?
|
||||
# ...just remember to use mouseRelease() if you work with hovering.
|
||||
|
||||
# Direction: Misc. Info
|
||||
direction = 2
|
||||
|
||||
pos = QtGui.QCursor.pos()
|
||||
wgt = QtGui.QApplication.widgetAt(pos)
|
||||
if wgt is None:
|
||||
# Don't set None, for now. May change this later.
|
||||
self.addMessage("You need to have your cursor over something " + \
|
||||
"in Pesterchum to use that.",
|
||||
direction=direction)
|
||||
return
|
||||
|
||||
self.selected_widget = wgt
|
||||
nchild = len(wgt.children())
|
||||
output = []
|
||||
output.append("CONSOLE.selected_widget = {0!r}".format(wgt))
|
||||
output.append("{0: <4}Parent: {1!r}".format('', wgt.parent()))
|
||||
output.append("{0: <4}{1:4d} child{2}".format('',
|
||||
nchild, ("ren" if abs(nchild) != 1 else "") ))
|
||||
if self.show_info_on_select:
|
||||
qtss = None
|
||||
uses_ss = None
|
||||
try:
|
||||
qtss = wgt.styleSheet()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if unicode(qtss) == unicode(""):
|
||||
uses_ss, ss_msg = False, "No"
|
||||
elif qtss is not None:
|
||||
uses_ss, ss_msg = True, "Yes"
|
||||
else:
|
||||
uses_ss, ss_msg = None, "Invalid"
|
||||
|
||||
ss_par, ss_par_msg = None, ""
|
||||
if uses_ss is False:
|
||||
# TODO: Split this into a sub-function or integrate it into
|
||||
# Styler or *something*.
|
||||
# The stylesheet was probably defined on a parent higher up.
|
||||
# Rungs above the start
|
||||
i = 0
|
||||
# qtss is still "" from earlier
|
||||
while not qtss:
|
||||
try:
|
||||
ss_par = wgt.parent()
|
||||
qtss = ss_par.styleSheet()
|
||||
except:
|
||||
# Can't ascend...and we're still in loop, so we don't
|
||||
# have what we came for.
|
||||
# Either that, or it's incompatible, which means the
|
||||
# ones above are anyway.
|
||||
ss_par = False
|
||||
break
|
||||
else:
|
||||
# Indicate that we got this from a parent
|
||||
i += 1
|
||||
|
||||
if not qtss:
|
||||
# There are no stylesheets here.
|
||||
if ss_par is False:
|
||||
# We had parent issues.
|
||||
# TODO: Specifically indicate invalid parent.
|
||||
uses_ss, ss_msg = None, "Invalid"
|
||||
else:
|
||||
uses_ss, ss_msg = False, "No"
|
||||
else:
|
||||
# We got a stylesheet out of this!
|
||||
uses_ss, ss_msg = True, "Yes"
|
||||
#~ss_par_msg = "{0: <4}...on parent ↑{1:d}: {2!r}".format('',
|
||||
ss_par_msg = "{0: <4}...on parent #{1:d}: {2!r}".format('',
|
||||
i, ss_par)
|
||||
|
||||
msg = []
|
||||
msg.append("{0: <4}QtSS?: {1}".format('', ss_msg))
|
||||
# A stylesheet analyzer would be wonderful here. Perhaps something
|
||||
# that tells us how many parent classes define stylesheets?
|
||||
if uses_ss:
|
||||
if ss_par_msg:
|
||||
# We got this stylesheet from a parent object somewhere.
|
||||
msg.append(ss_par_msg)
|
||||
msg.append("{0: <4}".format("Stylesheet:"))
|
||||
for ln in qtss.split('\n'):
|
||||
msg.append("{0: <8}".format(ln))
|
||||
|
||||
# Actually add this stuff to the result we're constructing
|
||||
output.extend(msg)
|
||||
|
||||
output = '\n'.join(output)
|
||||
self.addMessage(output, direction=direction)
|
||||
|
||||
|
||||
# Actual console stuff.
|
||||
def execInConsole(self, scriptstr, env=None):
|
||||
|
@ -147,7 +248,10 @@ class ConsoleWindow(QtGui.QDialog):
|
|||
"PCONFIG": self.mainwindow.config,
|
||||
"exit": lambda: self.mainwindow.exitaction.trigger()
|
||||
})
|
||||
_CUSTOM_ENV["quit"] = _CUSTOM_ENV["exit"]
|
||||
# Aliases.
|
||||
_CUSTOM_ENV.update({
|
||||
"quit": _CUSTOM_ENV["exit"]
|
||||
})
|
||||
# Add whatever additions were set in the main pesterchum file.
|
||||
_CUSTOM_ENV.update(pchum._CONSOLE_ENV)
|
||||
|
||||
|
@ -184,7 +288,7 @@ class ConsoleWindow(QtGui.QDialog):
|
|||
else:
|
||||
# No errors.
|
||||
if result is not None:
|
||||
print repr(result)
|
||||
print(repr(result))
|
||||
finally:
|
||||
# Restore system output.
|
||||
sys.stdout = sysout
|
||||
|
@ -208,13 +312,13 @@ class ConsoleWindow(QtGui.QDialog):
|
|||
|
||||
class ConsoleText(QtGui.QTextEdit):
|
||||
stylesheet_template = """
|
||||
QScrollBar:vertical {{ {style[convo/scrollbar/style]} }}
|
||||
QScrollBar::handle:vertical {{ {style[convo/scrollbar/handle]} }}
|
||||
QScrollBar::add-line:vertical {{ {style[convo/scrollbar/downarrow]} }}
|
||||
QScrollBar::sub-line:vertical {{ {style[convo/scrollbar/uparrow]} }}
|
||||
QScrollBar:up-arrow:vertical {{ {style[convo/scrollbar/uarrowstyle]} }}
|
||||
QScrollBar:down-arrow:vertical {{ {style[convo/scrollbar/darrowstyle]} }}
|
||||
"""
|
||||
QScrollBar:vertical {{ {style[convo/scrollbar/style]} }}
|
||||
QScrollBar::handle:vertical {{ {style[convo/scrollbar/handle]} }}
|
||||
QScrollBar::add-line:vertical {{ {style[convo/scrollbar/downarrow]} }}
|
||||
QScrollBar::sub-line:vertical {{ {style[convo/scrollbar/uparrow]} }}
|
||||
QScrollBar:up-arrow:vertical {{ {style[convo/scrollbar/uarrowstyle]} }}
|
||||
QScrollBar:down-arrow:vertical {{ {style[convo/scrollbar/darrowstyle]} }}
|
||||
"""
|
||||
stylesheet_path = "convo/textarea/style"
|
||||
# NOTE: Qt applies stylesheets like switching CSS files. They are NOT
|
||||
# applied piecemeal.
|
||||
|
@ -283,7 +387,10 @@ class ConsoleText(QtGui.QTextEdit):
|
|||
timestamp = ""
|
||||
|
||||
# Figure out what prefix to use.
|
||||
if direction > 0:
|
||||
if direction > 1:
|
||||
# Misc. Info
|
||||
prefix = parent.miscinfo_prefix
|
||||
elif direction > 0:
|
||||
# Outgoing.
|
||||
prefix = parent.outgoing_prefix
|
||||
elif direction < 0:
|
||||
|
|
|
@ -1184,21 +1184,29 @@ class PesterWindow(MovingWindow):
|
|||
self.menu = QtGui.QMenuBar(self)
|
||||
self.menu.setNativeMenuBar(False)
|
||||
|
||||
self.console = AttrDict()
|
||||
self.console.window = None
|
||||
self.console.action = QtGui.QAction("Console", self)
|
||||
self.console = AttrDict(dict(
|
||||
window = None,
|
||||
action = QtGui.QAction("Console", self),
|
||||
is_open = False
|
||||
))
|
||||
self.console.shortcuts = AttrDict(dict(
|
||||
conkey = QtGui.QShortcut(QtGui.QKeySequence("Ctrl+`"), self,
|
||||
context=QtCore.Qt.ApplicationShortcut),
|
||||
curwgt = QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Alt+w"), self,
|
||||
context=QtCore.Qt.ApplicationShortcut)
|
||||
))
|
||||
self.connect(self.console.action, QtCore.SIGNAL('triggered()'),
|
||||
self, QtCore.SLOT('toggleConsole()'))
|
||||
self.console.shortcut = QtGui.QShortcut(
|
||||
QtGui.QKeySequence("Ctrl+`"), self)
|
||||
# Make sure the shortcut works anywhere.
|
||||
# karxi: There's something wrong with the inheritance scheme here.
|
||||
self.console.shortcut.setContext(QtCore.Qt.ApplicationShortcut)
|
||||
self.connect(self.console.shortcut, QtCore.SIGNAL('activated()'),
|
||||
self, QtCore.SLOT('toggleConsole()'))
|
||||
#~self.console.shortcuts.conkey.setContext(QtCore.Qt.ApplicationShortcut)
|
||||
self.connect(self.console.shortcuts.conkey,
|
||||
QtCore.SIGNAL('activated()'), self, QtCore.SLOT('toggleConsole()'))
|
||||
#~# Use new-style connections
|
||||
#~self.console.shortcut.activated.connect(self.toggleConsole)
|
||||
# Apparently those can crash sometimes...c'est la vie. Can't use 'em.
|
||||
#~self.connect(self.console.shortcuts.curwgt,
|
||||
#~ QtCore.SIGNAL('activate()'), self.console.
|
||||
self.console.is_open = False
|
||||
|
||||
filemenu = self.menu.addMenu(self.theme["main/menus/client/_name"])
|
||||
|
@ -1589,9 +1597,35 @@ class PesterWindow(MovingWindow):
|
|||
logging.warning("Console made.")
|
||||
self.connect(win, QtCore.SIGNAL('windowClosed()'),
|
||||
self, QtCore.SLOT('consoleWindowClosed()'))
|
||||
self.connect(self.console.shortcuts.curwgt,
|
||||
QtCore.SIGNAL('activated()'),
|
||||
win, QtCore.SLOT('designateCurrentWidget()'))
|
||||
|
||||
if self.console.is_open:
|
||||
if not (win.hasFocus() or win.text.area.hasFocus() or
|
||||
win.text.input.hasFocus()):
|
||||
for wgt in win.findChildren(QtGui.QWidget):
|
||||
try:
|
||||
focused = wgt.hasFocus()
|
||||
except AttributeError:
|
||||
# The widget doesn't have a hasFocus method.
|
||||
# Just to reduce ambiguity
|
||||
focused = False
|
||||
# Try the next one.
|
||||
continue
|
||||
# No error, let's see if we're actually focused.
|
||||
if focused:
|
||||
logging.debug(
|
||||
"{0!r} is in focus (parent: {1!r}); hiding".format(
|
||||
wgt, wgt.parent())
|
||||
)
|
||||
# This widget, a child of our console, has focus.
|
||||
# So...the console has focus.
|
||||
# Set focus to the text input just for good measure.
|
||||
win.text.input.setFocus()
|
||||
# ...then hide it all.
|
||||
win.hide()
|
||||
self.console.is_open = False
|
||||
break
|
||||
else:
|
||||
# The console is open, but not in focus. Bring it to the fore.
|
||||
# NOTE: This is a bit of a kludgy method - it may not work
|
||||
# properly on Windows. Need to look into workarounds.
|
||||
|
@ -1600,14 +1634,15 @@ class PesterWindow(MovingWindow):
|
|||
# process/window that 'owns' this one changes our focus, since
|
||||
# the application ultimately already *has* our focus - but I'm
|
||||
# not sure.
|
||||
logging.debug("Console isn't in focus; fixing")
|
||||
win.raise_()
|
||||
win.show()
|
||||
win.activateWindow()
|
||||
else:
|
||||
# The console is open and in focus, so hide it for now.
|
||||
win.hide()
|
||||
self.console.is_open = False
|
||||
|
||||
win.text.input.setFocus()
|
||||
# No need to explicitly set it as open; it already is.
|
||||
else:
|
||||
logging.debug("Console not visible; showing")
|
||||
# Console isn't visible - show it.
|
||||
win.show()
|
||||
self.console.is_open = True
|
||||
|
|
Loading…
Reference in a new issue