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:
karxi 2017-01-10 19:53:26 -05:00
parent cf37cb6c36
commit 195b59f29e
3 changed files with 173 additions and 28 deletions

View file

@ -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

View file

@ -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
@ -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:

View file

@ -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()
win.text.input.setFocus()
# No need to explicitly set it as open; it already is.
else:
# The console is open and in focus, so hide it for now.
win.hide()
self.console.is_open = False
else:
logging.debug("Console not visible; showing")
# Console isn't visible - show it.
win.show()
self.console.is_open = True