diff --git a/TODO.mkdn b/TODO.mkdn index 2c74cd5..bb906ed 100644 --- a/TODO.mkdn +++ b/TODO.mkdn @@ -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 diff --git a/console.py b/console.py index 95a43db..7b62696 100644 --- a/console.py +++ b/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: diff --git a/pesterchum.py b/pesterchum.py index 4697fb3..5387cf8 100644 --- a/pesterchum.py +++ b/pesterchum.py @@ -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