pesterchum/menus.py
2023-07-23 20:16:21 +02:00

2266 lines
90 KiB
Python

import re
import logging
from os import remove
try:
from PyQt6 import QtCore, QtGui, QtWidgets, QtMultimedia
from PyQt6.QtGui import QAction
except ImportError:
print("PyQt5 fallback (menus.py)")
from PyQt5 import QtCore, QtGui, QtWidgets, QtMultimedia
from PyQt5.QtWidgets import QAction
import ostools
import parsetools
from theme_repo_manager import ThemeManagerWidget
from generic import RightClickList, RightClickTree, MultiTextDialog
from dataobjs import pesterQuirk, PesterProfile, PesterHistory
from memos import TimeSlider, TimeInput
from version import _pcVersion
from convo import PesterInput, PesterText
from parsetools import lexMessage
_datadir = ostools.getDataDir()
# Logger
PchumLog = logging.getLogger("pchumLogger")
class PesterQuirkItem(QtWidgets.QTreeWidgetItem):
def __init__(self, quirk):
parent = None
QtWidgets.QTreeWidgetItem.__init__(self, parent)
self.quirk = quirk
self.setText(0, str(quirk)) # Typecast required.
def update(self, quirk):
self.quirk = quirk
self.setText(0, str(quirk))
def __lt__(self, quirkitem):
"""Sets the order of quirks if auto-sorted by Qt. Obsolete now."""
if self.quirk.type == "prefix":
return True
elif self.quirk.type in ("replace", "regexp") and quirkitem.type == "suffix":
return True
else:
return False
class PesterQuirkList(QtWidgets.QTreeWidget):
def __init__(self, mainwindow, parent):
QtWidgets.QTreeWidget.__init__(self, parent)
self.resize(400, 200)
# make sure we have access to mainwindow info like profiles
self.mainwindow = mainwindow
self.setStyleSheet("background:black; color:white;")
self.itemChanged.connect(
self.changeCheckState
) # [QtWidgets.QTreeWidgetItem, int]
for q in mainwindow.userprofile.quirks:
item = PesterQuirkItem(q)
self.addItem(item, False)
self.changeCheckState()
# self.setDragEnabled(True)
# self.setDragDropMode(QtGui.QAbstractItemView.DragDropMode.InternalMove)
self.setDropIndicatorShown(True)
self.setSortingEnabled(False)
self.setIndentation(15)
self.header().hide()
def addItem(self, item, new=True):
item.setFlags(
QtCore.Qt.ItemFlag.ItemIsSelectable
| QtCore.Qt.ItemFlag.ItemIsDragEnabled
| QtCore.Qt.ItemFlag.ItemIsUserCheckable
| QtCore.Qt.ItemFlag.ItemIsEnabled
)
if item.quirk.on:
item.setCheckState(0, QtCore.Qt.CheckState.Checked)
else:
item.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
if new:
curgroup = self.currentItem()
if curgroup:
if curgroup.parent():
curgroup = curgroup.parent()
item.quirk.quirk["group"] = item.quirk.group = curgroup.text(0)
found = self.findItems(item.quirk.group, QtCore.Qt.MatchFlag.MatchExactly)
if len(found) > 0:
found[0].addChild(item)
else:
child_1 = QtWidgets.QTreeWidgetItem([item.quirk.group])
self.addTopLevelItem(child_1)
child_1.setFlags(
child_1.flags()
| QtCore.Qt.ItemFlag.ItemIsUserCheckable
| QtCore.Qt.ItemFlag.ItemIsEnabled
)
child_1.setChildIndicatorPolicy(
QtWidgets.QTreeWidgetItem.ChildIndicatorPolicy.DontShowIndicatorWhenChildless
)
child_1.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
child_1.setExpanded(True)
child_1.addChild(item)
self.changeCheckState()
def currentQuirk(self):
if isinstance(self.currentItem(), PesterQuirkItem):
return self.currentItem()
else:
return None
@QtCore.pyqtSlot()
def upShiftQuirk(self):
# We cannot move the quirk up if there are no selected quirks,
# or if there are none period.
if self.currentItem() is None:
return
found = self.findItems(
self.currentItem().text(0), QtCore.Qt.MatchFlag.MatchExactly
)
if len(found): # group
i = self.indexOfTopLevelItem(found[0])
if i > 0:
expand = found[0].isExpanded()
shifted_item = self.takeTopLevelItem(i)
self.insertTopLevelItem(i - 1, shifted_item)
shifted_item.setExpanded(expand)
self.setCurrentItem(shifted_item)
else: # quirk
found = self.findItems(
self.currentItem().text(0),
QtCore.Qt.MatchFlag.MatchExactly | QtCore.Qt.MatchFlag.MatchRecursive,
)
for f in found:
if not f.isSelected():
continue
if not f.parent():
continue
i = f.parent().indexOfChild(f)
if i > 0: # keep in same group
p = f.parent()
shifted_item = f.parent().takeChild(i)
p.insertChild(i - 1, shifted_item)
self.setCurrentItem(shifted_item)
else: # move to another group
j = self.indexOfTopLevelItem(f.parent())
if j <= 0:
continue
shifted_item = f.parent().takeChild(i)
self.topLevelItem(j - 1).addChild(shifted_item)
self.setCurrentItem(shifted_item)
self.changeCheckState()
@QtCore.pyqtSlot()
def downShiftQuirk(self):
# We cannot move the quirk down if there are no selected quirks,
# or if there are none period.
if self.currentItem() is None:
return
found = self.findItems(
self.currentItem().text(0), QtCore.Qt.MatchFlag.MatchExactly
)
if len(found): # group
i = self.indexOfTopLevelItem(found[0])
if i < self.topLevelItemCount() - 1 and i >= 0:
expand = found[0].isExpanded()
shifted_item = self.takeTopLevelItem(i)
self.insertTopLevelItem(i + 1, shifted_item)
shifted_item.setExpanded(expand)
self.setCurrentItem(shifted_item)
else: # quirk
found = self.findItems(
self.currentItem().text(0),
QtCore.Qt.MatchFlag.MatchExactly | QtCore.Qt.MatchFlag.MatchRecursive,
)
for f in found:
if not f.isSelected():
continue
if not f.parent():
continue
i = f.parent().indexOfChild(f)
if i < f.parent().childCount() - 1 and i >= 0:
p = f.parent()
shifted_item = f.parent().takeChild(i)
p.insertChild(i + 1, shifted_item)
self.setCurrentItem(shifted_item)
else:
j = self.indexOfTopLevelItem(f.parent())
if j >= self.topLevelItemCount() - 1 or j < 0:
continue
shifted_item = f.parent().takeChild(i)
self.topLevelItem(j + 1).insertChild(0, shifted_item)
self.setCurrentItem(shifted_item)
self.changeCheckState()
@QtCore.pyqtSlot()
def removeCurrent(self):
i = self.currentItem()
found = self.findItems(
i.text(0),
QtCore.Qt.MatchFlag.MatchExactly | QtCore.Qt.MatchFlag.MatchRecursive,
)
for f in found:
if not f.isSelected():
continue
if not f.parent(): # group
msgbox = QtWidgets.QMessageBox()
msgbox.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"])
msgbox.setObjectName("delquirkwarning")
msgbox.setWindowTitle("WARNING!")
msgbox.setInformativeText(
"Are you sure you want to delete the quirk group: %s" % (f.text(0))
)
msgbox.setStandardButtons(
QtWidgets.QMessageBox.StandardButton.Ok
| QtWidgets.QMessageBox.StandardButton.Cancel
)
# Find the Cancel button and make it default
for b in msgbox.buttons():
if (
msgbox.buttonRole(b)
== QtWidgets.QMessageBox.ButtonRole.RejectRole
):
# We found the 'OK' button, set it as the default
b.setDefault(True)
b.setAutoDefault(True)
# Actually set it as the selected option, since we're
# already stealing focus
b.setFocus()
break
ret = msgbox.exec()
if ret == QtWidgets.QMessageBox.StandardButton.Ok:
self.takeTopLevelItem(self.indexOfTopLevelItem(f))
else:
f.parent().takeChild(f.parent().indexOfChild(f))
self.changeCheckState()
@QtCore.pyqtSlot()
def addQuirkGroup(self):
if not hasattr(self, "addgroupdialog"):
self.addgroupdialog = None
if not self.addgroupdialog:
(gname, ok) = QtWidgets.QInputDialog.getText(
self, "Add Group", "Enter a name for the new quirk group:"
)
if ok:
if re.search(r"[^A-Za-z0-9_\s]", gname) is not None:
msgbox = QtWidgets.QMessageBox()
msgbox.setInformativeText("THIS IS NOT A VALID GROUP NAME")
msgbox.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok)
msgbox.exec()
self.addgroupdialog = None
return
found = self.findItems(gname, QtCore.Qt.MatchFlag.MatchExactly)
if found:
msgbox = QtWidgets.QMessageBox()
msgbox.setInformativeText("THIS QUIRK GROUP ALREADY EXISTS")
msgbox.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok)
msgbox.exec()
return
child_1 = QtWidgets.QTreeWidgetItem([gname])
self.addTopLevelItem(child_1)
child_1.setFlags(
child_1.flags()
| QtCore.Qt.ItemFlag.ItemIsUserCheckable
| QtCore.Qt.ItemFlag.ItemIsEnabled
)
child_1.setChildIndicatorPolicy(
QtWidgets.QTreeWidgetItem.ChildIndicatorPolicy.DontShowIndicatorWhenChildless
)
child_1.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
child_1.setExpanded(True)
self.addgroupdialog = None
@QtCore.pyqtSlot()
def changeCheckState(self):
index = self.indexOfTopLevelItem(self.currentItem())
if index == -1:
for i in range(self.topLevelItemCount()):
allChecked = True
noneChecked = True
for j in range(self.topLevelItem(i).childCount()):
if self.topLevelItem(i).child(j).checkState(0):
noneChecked = False
else:
allChecked = False
if allChecked:
self.topLevelItem(i).setCheckState(0, QtCore.Qt.CheckState.Checked)
elif noneChecked:
self.topLevelItem(i).setCheckState(
0, QtCore.Qt.CheckState.PartiallyChecked
)
else:
self.topLevelItem(i).setCheckState(0, QtCore.Qt.CheckState.Checked)
else:
state = self.topLevelItem(index).checkState(0)
for j in range(self.topLevelItem(index).childCount()):
self.topLevelItem(index).child(j).setCheckState(0, state)
class QuirkTesterWindow(QtWidgets.QDialog):
def __init__(self, parent):
QtWidgets.QDialog.__init__(self, parent)
self.prnt = parent
self.mainwindow = parent.mainwindow
self.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"])
self.setWindowTitle("Quirk Tester")
self.resize(350, 300)
self.textArea = PesterText(self.mainwindow.theme, self)
self.textInput = PesterInput(self.mainwindow.theme, self)
self.textInput.setFocus()
self.textInput.returnPressed.connect(self.sentMessage)
self.chumopen = True
self.chum = self.mainwindow.profile()
self.history = PesterHistory()
layout_0 = QtWidgets.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()
return parsetools.kxhandleInput(
self,
text,
"menus",
irc_compatible=self.mainwindow.config.irc_compatibility_mode(),
)
def addMessage(self, msg, me=True):
if isinstance(msg, str):
lexmsg = lexMessage(msg)
else:
lexmsg = msg
if me:
chum = self.mainwindow.profile()
else:
chum = self.chum
self.textArea.addMessage(lexmsg, chum)
def closeEvent(self, event):
self.parent().quirktester = None
class PesterQuirkTypes(QtWidgets.QDialog):
def __init__(self, parent, quirk=None):
QtWidgets.QDialog.__init__(self, parent)
self.mainwindow = parent.mainwindow
self.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"])
self.setWindowTitle("Quirk Wizard")
self.resize(500, 310)
self.quirk = quirk
self.pages = QtWidgets.QStackedWidget(self)
self.next = QtWidgets.QPushButton("Next", self)
self.next.setDefault(True)
self.next.clicked.connect(self.nextPage)
self.back = QtWidgets.QPushButton("Back", self)
self.back.setEnabled(False)
self.back.clicked.connect(self.backPage)
self.cancel = QtWidgets.QPushButton("Cancel", self)
self.cancel.clicked.connect(self.reject)
layout_2 = QtWidgets.QHBoxLayout()
layout_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
layout_2.addWidget(self.back)
layout_2.addWidget(self.next)
layout_2.addSpacing(5)
layout_2.addWidget(self.cancel)
vr = QtWidgets.QFrame()
vr.setFrameShape(QtWidgets.QFrame.Shape.VLine)
vr.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
vr2 = QtWidgets.QFrame()
vr2.setFrameShape(QtWidgets.QFrame.Shape.VLine)
vr2.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
self.funclist = QtWidgets.QListWidget(self)
self.funclist.setStyleSheet("color: #000000; background-color: #FFFFFF;")
self.funclist2 = QtWidgets.QListWidget(self)
self.funclist2.setStyleSheet("color: #000000; background-color: #FFFFFF;")
from parsetools import quirkloader
funcs = [q + "()" for q in list(quirkloader.quirks.keys())]
funcs.sort()
self.funclist.addItems(funcs)
self.funclist2.addItems(funcs)
self.reloadQuirkFuncButton = QtWidgets.QPushButton("RELOAD FUNCTIONS", self)
self.reloadQuirkFuncButton.clicked.connect(self.reloadQuirkFuncSlot)
self.reloadQuirkFuncButton2 = QtWidgets.QPushButton("RELOAD FUNCTIONS", self)
self.reloadQuirkFuncButton2.clicked.connect(self.reloadQuirkFuncSlot)
self.funclist.setMaximumWidth(160)
self.funclist.resize(160, 50)
self.funclist2.setMaximumWidth(160)
self.funclist2.resize(160, 50)
layout_f = QtWidgets.QVBoxLayout()
layout_f.addWidget(QtWidgets.QLabel("Available Regexp\nFunctions"))
layout_f.addWidget(self.funclist)
layout_f.addWidget(self.reloadQuirkFuncButton)
layout_g = QtWidgets.QVBoxLayout()
layout_g.addWidget(QtWidgets.QLabel("Available Regexp\nFunctions"))
layout_g.addWidget(self.funclist2)
layout_g.addWidget(self.reloadQuirkFuncButton2)
# Pages
# Type select
widget = QtWidgets.QWidget()
self.pages.addWidget(widget)
layout_select = QtWidgets.QVBoxLayout(widget)
layout_select.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
self.radios = []
self.radios.append(QtWidgets.QRadioButton("Prefix", self))
self.radios.append(QtWidgets.QRadioButton("Suffix", self))
self.radios.append(QtWidgets.QRadioButton("Simple Replace", self))
self.radios.append(QtWidgets.QRadioButton("Regexp Replace", self))
self.radios.append(QtWidgets.QRadioButton("Random Replace", self))
self.radios.append(QtWidgets.QRadioButton("Mispeller", self))
layout_select.addWidget(QtWidgets.QLabel("Select Quirk Type:"))
for r in self.radios:
layout_select.addWidget(r)
# Prefix
widget = QtWidgets.QWidget()
self.pages.addWidget(widget)
layout_prefix = QtWidgets.QVBoxLayout(widget)
layout_prefix.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_prefix.addWidget(QtWidgets.QLabel("Prefix"))
layout_3 = QtWidgets.QHBoxLayout()
layout_3.addWidget(QtWidgets.QLabel("Value:"))
layout_3.addWidget(QtWidgets.QLineEdit())
layout_prefix.addLayout(layout_3)
# Suffix
widget = QtWidgets.QWidget()
self.pages.addWidget(widget)
layout_suffix = QtWidgets.QVBoxLayout(widget)
layout_suffix.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_suffix.addWidget(QtWidgets.QLabel("Suffix"))
layout_3 = QtWidgets.QHBoxLayout()
layout_3.addWidget(QtWidgets.QLabel("Value:"))
layout_3.addWidget(QtWidgets.QLineEdit())
layout_suffix.addLayout(layout_3)
# Simple Replace
widget = QtWidgets.QWidget()
self.pages.addWidget(widget)
layout_replace = QtWidgets.QVBoxLayout(widget)
layout_replace.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_replace.addWidget(QtWidgets.QLabel("Simple Replace"))
layout_3 = QtWidgets.QHBoxLayout()
layout_3.addWidget(QtWidgets.QLabel("Replace:"))
layout_3.addWidget(QtWidgets.QLineEdit())
layout_replace.addLayout(layout_3)
layout_3 = QtWidgets.QHBoxLayout()
layout_3.addWidget(QtWidgets.QLabel("With:"))
layout_3.addWidget(QtWidgets.QLineEdit())
layout_replace.addLayout(layout_3)
layout_3 = QtWidgets.QHBoxLayout()
excludeCheckbox = QtWidgets.QCheckBox("Exclude links and smilies")
excludeCheckbox.setToolTip(
"Splits input to exclude smilies, weblinks, @handles, and #memos."
+ "\nThe replace is applied on every substring individually."
)
layout_3.addWidget(excludeCheckbox)
layout_replace.addLayout(layout_3)
# Regexp Replace
widget = QtWidgets.QWidget()
self.pages.addWidget(widget)
layout_all = QtWidgets.QHBoxLayout(widget)
layout_regexp = QtWidgets.QVBoxLayout()
layout_regexp.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_regexp.addWidget(QtWidgets.QLabel("Regexp Replace"))
layout_3 = QtWidgets.QHBoxLayout()
layout_3.addWidget(QtWidgets.QLabel("Regexp:"))
layout_3.addWidget(QtWidgets.QLineEdit())
layout_regexp.addLayout(layout_3)
layout_3 = QtWidgets.QHBoxLayout()
layout_3.addWidget(QtWidgets.QLabel("Replace With:"))
layout_3.addWidget(QtWidgets.QLineEdit())
layout_regexp.addLayout(layout_3)
layout_3 = QtWidgets.QHBoxLayout()
excludeCheckbox = QtWidgets.QCheckBox("Exclude links and smilies")
excludeCheckbox.setToolTip(
"Splits input to exclude smilies, weblinks, @handles, and #memos."
+ "\nSince the replace is applied on every substring individually,"
+ "\ncertain patterns or functions like gradients may not work correctly."
)
layout_3.addWidget(excludeCheckbox)
layout_regexp.addLayout(layout_3)
layout_all.addLayout(layout_f)
layout_all.addWidget(vr)
layout_all.addLayout(layout_regexp)
# Random Replace
widget = QtWidgets.QWidget()
self.pages.addWidget(widget)
layout_all = QtWidgets.QHBoxLayout(widget)
layout_random = QtWidgets.QVBoxLayout()
layout_random.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_random.addWidget(QtWidgets.QLabel("Random Replace"))
layout_5 = QtWidgets.QHBoxLayout()
regexpl = QtWidgets.QLabel("Regexp:", self)
self.regexp = QtWidgets.QLineEdit("", self)
layout_5.addWidget(regexpl)
layout_5.addWidget(self.regexp)
replacewithl = QtWidgets.QLabel("Replace With:", self)
layout_all.addLayout(layout_g)
layout_all.addWidget(vr2)
layout_all.addLayout(layout_random)
layout_6 = QtWidgets.QVBoxLayout()
layout_7 = QtWidgets.QHBoxLayout()
self.replacelist = QtWidgets.QListWidget(self)
self.replaceinput = QtWidgets.QLineEdit(self)
addbutton = QtWidgets.QPushButton("ADD", self)
addbutton.clicked.connect(self.addRandomString)
removebutton = QtWidgets.QPushButton("REMOVE", self)
removebutton.clicked.connect(self.removeRandomString)
layout_7.addWidget(addbutton)
layout_7.addWidget(removebutton)
layout_6.addLayout(layout_5)
layout_6.addWidget(replacewithl)
layout_6.addWidget(self.replacelist)
layout_6.addWidget(self.replaceinput)
layout_6.addLayout(layout_7)
layout_random.addLayout(layout_6)
layout_9 = QtWidgets.QHBoxLayout()
excludeCheckbox = QtWidgets.QCheckBox("Exclude links and smilies")
excludeCheckbox.setToolTip(
"Splits input to exclude smilies, weblinks, @handles, and #memos."
+ "\nSince the replace is applied on every substring individually,"
+ "\ncertain patterns or functions like gradients may not work correctly."
)
layout_9.addWidget(excludeCheckbox)
layout_random.addLayout(layout_9)
# Misspeller
widget = QtWidgets.QWidget()
self.pages.addWidget(widget)
layout_mispeller = QtWidgets.QVBoxLayout(widget)
layout_mispeller.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_mispeller.addWidget(QtWidgets.QLabel("Mispeller"))
layout_1 = QtWidgets.QHBoxLayout()
zero = QtWidgets.QLabel("1%", self)
hund = QtWidgets.QLabel("100%", self)
self.current = QtWidgets.QLabel("50%", self)
self.current.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter)
self.slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal, self)
self.slider.setMinimum(1)
self.slider.setMaximum(100)
self.slider.setValue(50)
self.slider.valueChanged[int].connect(self.printValue)
layout_1.addWidget(zero)
layout_1.addWidget(self.slider)
layout_1.addWidget(hund)
layout_mispeller.addLayout(layout_1)
layout_mispeller.addWidget(self.current)
layout_0 = QtWidgets.QVBoxLayout()
layout_0.addWidget(self.pages)
layout_0.addLayout(layout_2)
layout_3 = QtWidgets.QHBoxLayout()
excludeCheckbox = QtWidgets.QCheckBox("Exclude links and smilies")
excludeCheckbox.setToolTip(
"Splits input to exclude smilies, weblinks, @handles, and #memos."
+ "\nThe replace is applied on every substring individually."
)
layout_3.addWidget(excludeCheckbox)
layout_mispeller.addLayout(layout_3)
if quirk:
types = ["prefix", "suffix", "replace", "regexp", "random", "spelling"]
for i, r in enumerate(self.radios):
if i == types.index(quirk.quirk.type):
r.setChecked(True)
self.changePage(types.index(quirk.quirk.type) + 1)
page = self.pages.currentWidget().layout()
q = quirk.quirk.quirk
if q["type"] in ("prefix", "suffix"):
page.itemAt(1).layout().itemAt(1).widget().setText(q["value"])
elif q["type"] == "replace":
page.itemAt(1).layout().itemAt(1).widget().setText(q["from"])
page.itemAt(2).layout().itemAt(1).widget().setText(q["to"])
try:
page.itemAt(3).layout().itemAt(0).widget().setCheckState(
QtCore.Qt.CheckState(int(q["checkstate"]))
)
except (KeyError, ValueError):
PchumLog.exception("Exception setting replace quirk.")
elif q["type"] == "regexp":
page.itemAt(2).layout().itemAt(1).layout().itemAt(1).widget().setText(
q["from"]
)
page.itemAt(2).layout().itemAt(2).layout().itemAt(1).widget().setText(
q["to"]
)
try:
page.itemAt(2).layout().itemAt(3).layout().itemAt(
0
).widget().setCheckState(QtCore.Qt.CheckState(int(q["checkstate"])))
except (KeyError, ValueError):
PchumLog.exception("Exception setting regexp quirk.")
elif q["type"] == "random":
self.regexp.setText(q["from"])
for v in q["randomlist"]:
item = QtWidgets.QListWidgetItem(v, self.replacelist)
try:
page.itemAt(2).layout().itemAt(2).layout().itemAt(
0
).widget().setCheckState(QtCore.Qt.CheckState(int(q["checkstate"])))
except (KeyError, ValueError):
PchumLog.exception("Exception setting random quirk.")
elif q["type"] == "spelling":
self.slider.setValue(q["percentage"])
try:
page.itemAt(3).layout().itemAt(0).widget().setCheckState(
QtCore.Qt.CheckState(int(q["checkstate"]))
)
except (KeyError, ValueError) as e:
PchumLog.exception("Exception setting spelling quirk.")
self.setLayout(layout_0)
def closeEvent(self, event):
self.parent().quirkadd = None
def changePage(self, page):
c = self.pages.count()
if page >= c or page < 0:
return
self.back.setEnabled(page > 0)
if page >= 1 and page <= 6:
self.next.setText("Finish")
else:
self.next.setText("Next")
self.pages.setCurrentIndex(page)
@QtCore.pyqtSlot()
def nextPage(self):
if self.next.text() == "Finish":
self.accept()
return
cur = self.pages.currentIndex()
if cur == 0:
for i, r in enumerate(self.radios):
if r.isChecked():
self.changePage(i + 1)
else:
self.changePage(cur + 1)
@QtCore.pyqtSlot()
def backPage(self):
cur = self.pages.currentIndex()
if cur >= 1 and cur <= 6:
self.changePage(0)
@QtCore.pyqtSlot(int)
def printValue(self, value):
self.current.setText(f"{value}%")
@QtCore.pyqtSlot()
def addRandomString(self):
text = self.replaceinput.text()
item = QtWidgets.QListWidgetItem(text, self.replacelist)
self.replaceinput.setText("")
self.replaceinput.setFocus()
@QtCore.pyqtSlot()
def removeRandomString(self):
if not self.replacelist.currentItem():
return
else:
self.replacelist.takeItem(self.replacelist.currentRow())
self.replaceinput.setFocus()
@QtCore.pyqtSlot()
def reloadQuirkFuncSlot(self):
from parsetools import reloadQuirkFunctions, quirkloader
reloadQuirkFunctions()
funcs = [q + "()" for q in list(quirkloader.quirks.keys())]
funcs.sort()
self.funclist.clear()
self.funclist.addItems(funcs)
self.funclist2.clear()
self.funclist2.addItems(funcs)
class PesterChooseQuirks(QtWidgets.QDialog):
def __init__(self, config, theme, parent):
QtWidgets.QDialog.__init__(self, parent)
self.setModal(False)
self.config = config
self.theme = theme
self.mainwindow = parent
self.setStyleSheet(self.theme["main/defaultwindow/style"])
self.setWindowTitle("Set Quirks")
self.quirkList = PesterQuirkList(self.mainwindow, self)
self.addQuirkButton = QtWidgets.QPushButton("ADD QUIRK", self)
self.addQuirkButton.clicked.connect(self.addQuirkDialog)
self.upShiftButton = QtWidgets.QPushButton("^", self)
self.downShiftButton = QtWidgets.QPushButton("v", self)
self.upShiftButton.setToolTip("Move quirk up one")
self.downShiftButton.setToolTip("Move quirk down one")
self.upShiftButton.clicked.connect(self.quirkList.upShiftQuirk)
self.downShiftButton.clicked.connect(self.quirkList.downShiftQuirk)
self.newGroupButton = QtWidgets.QPushButton("*", self)
self.newGroupButton.setToolTip("New Quirk Group")
self.newGroupButton.clicked.connect(self.quirkList.addQuirkGroup)
layout_quirklist = QtWidgets.QHBoxLayout() # the nude layout quirklist
layout_shiftbuttons = QtWidgets.QVBoxLayout() # the shift button layout
layout_shiftbuttons.addWidget(self.upShiftButton)
layout_shiftbuttons.addWidget(self.newGroupButton)
layout_shiftbuttons.addWidget(self.downShiftButton)
layout_quirklist.addWidget(self.quirkList)
layout_quirklist.addLayout(layout_shiftbuttons)
layout_1 = QtWidgets.QHBoxLayout()
layout_1.addWidget(self.addQuirkButton)
self.editSelectedButton = QtWidgets.QPushButton("EDIT", self)
self.editSelectedButton.clicked.connect(self.editSelected)
self.removeSelectedButton = QtWidgets.QPushButton("REMOVE", self)
self.removeSelectedButton.clicked.connect(self.quirkList.removeCurrent)
layout_3 = QtWidgets.QHBoxLayout()
layout_3.addWidget(self.editSelectedButton)
layout_3.addWidget(self.removeSelectedButton)
self.ok = QtWidgets.QPushButton("OK", self)
self.ok.setDefault(True)
self.ok.clicked.connect(self.accept)
self.test = QtWidgets.QPushButton("TEST QUIRKS", self)
self.test.clicked.connect(self.testQuirks)
self.cancel = QtWidgets.QPushButton("CANCEL", self)
self.cancel.clicked.connect(self.reject)
layout_ok = QtWidgets.QHBoxLayout()
layout_ok.addWidget(self.cancel)
layout_ok.addWidget(self.test)
layout_ok.addWidget(self.ok)
layout_0 = QtWidgets.QVBoxLayout()
layout_0.addLayout(layout_quirklist)
layout_0.addLayout(layout_1)
# layout_0.addLayout(layout_2)
layout_0.addLayout(layout_3)
layout_0.addLayout(layout_ok)
self.setLayout(layout_0)
def quirks(self):
u = []
for i in range(self.quirkList.topLevelItemCount()):
for j in range(self.quirkList.topLevelItem(i).childCount()):
u.append(self.quirkList.topLevelItem(i).child(j).quirk)
return u
# return [self.quirkList.item(i).quirk for i in range(self.quirkList.count())]
def testquirks(self):
u = []
for i in range(self.quirkList.topLevelItemCount()):
for j in range(self.quirkList.topLevelItem(i).childCount()):
item = self.quirkList.topLevelItem(i).child(j)
if item.checkState(0) == QtCore.Qt.CheckState.Checked:
u.append(item.quirk)
return u
@QtCore.pyqtSlot()
def testQuirks(self):
if not hasattr(self, "quirktester"):
self.quirktester = None
if self.quirktester:
return
self.quirktester = QuirkTesterWindow(self)
self.quirktester.show()
@QtCore.pyqtSlot()
def editSelected(self):
q = self.quirkList.currentQuirk()
if not q:
return
# quirk = q.quirk
self.addQuirkDialog(q)
@QtCore.pyqtSlot()
def addQuirkDialog(self, quirk=None):
if not hasattr(self, "quirkadd"):
self.quirkadd = None
if self.quirkadd:
return
self.quirkadd = PesterQuirkTypes(self, quirk)
self.quirkadd.accepted.connect(self.addQuirk)
self.quirkadd.rejected.connect(self.closeQuirk)
self.quirkadd.show()
@QtCore.pyqtSlot()
def addQuirk(self):
types = ["prefix", "suffix", "replace", "regexp", "random", "spelling"]
vdict = {}
vdict["type"] = types[self.quirkadd.pages.currentIndex() - 1]
page = self.quirkadd.pages.currentWidget().layout()
if vdict["type"] in ("prefix", "suffix"):
vdict["value"] = page.itemAt(1).layout().itemAt(1).widget().text()
elif vdict["type"] == "replace":
vdict["from"] = page.itemAt(1).layout().itemAt(1).widget().text()
vdict["to"] = page.itemAt(2).layout().itemAt(1).widget().text()
try:
# PyQt6
vdict["checkstate"] = str(
page.itemAt(3).layout().itemAt(0).widget().checkState().value
)
except AttributeError:
# PyQt5
vdict["checkstate"] = str(
page.itemAt(3).layout().itemAt(0).widget().checkState()
)
elif vdict["type"] == "regexp":
vdict["from"] = (
page.itemAt(2).layout().itemAt(1).layout().itemAt(1).widget().text()
)
vdict["to"] = (
page.itemAt(2).layout().itemAt(2).layout().itemAt(1).widget().text()
)
try:
# PyQt6
vdict["checkstate"] = str(
page.itemAt(2)
.layout()
.itemAt(3)
.layout()
.itemAt(0)
.widget()
.checkState()
.value
)
except AttributeError:
# PyQt5
vdict["checkstate"] = str(
page.itemAt(2)
.layout()
.itemAt(3)
.layout()
.itemAt(0)
.widget()
.checkState()
)
elif vdict["type"] == "random":
vdict["from"] = str(self.quirkadd.regexp.text())
try:
# PyQt6
vdict["checkstate"] = str(
page.itemAt(2)
.layout()
.itemAt(2)
.layout()
.itemAt(0)
.widget()
.checkState()
.value
)
except AttributeError:
# PyQt5
vdict["checkstate"] = str(
page.itemAt(2)
.layout()
.itemAt(2)
.layout()
.itemAt(0)
.widget()
.checkState()
)
randomlist = [
str(self.quirkadd.replacelist.item(i).text())
for i in range(0, self.quirkadd.replacelist.count())
]
vdict["randomlist"] = randomlist
elif vdict["type"] == "spelling":
vdict["percentage"] = self.quirkadd.slider.value()
try:
# PyQt6
vdict["checkstate"] = str(
page.itemAt(3).layout().itemAt(0).widget().checkState().value
)
except AttributeError:
# PyQt5
vdict["checkstate"] = str(
page.itemAt(3).layout().itemAt(0).widget().checkState()
)
if vdict["type"] in ("regexp", "random"):
try:
re.compile(vdict["from"])
except re.error as e:
quirkWarning = QtWidgets.QMessageBox(self)
quirkWarning.setText("Not a valid regular expression!")
quirkWarning.setInformativeText("H3R3S WHY DUMP4SS: %s" % (e))
quirkWarning.exec()
self.quirkadd = None
return
quirk = pesterQuirk(vdict)
if self.quirkadd.quirk is None:
item = PesterQuirkItem(quirk)
self.quirkList.addItem(item)
else:
self.quirkadd.quirk.update(quirk)
self.quirkadd = None
@QtCore.pyqtSlot()
def closeQuirk(self):
self.quirkadd = None
class PesterChooseTheme(QtWidgets.QDialog):
def __init__(self, config, theme, parent):
QtWidgets.QDialog.__init__(self, parent)
self.config = config
self.theme = theme
self.parent = parent
self.setStyleSheet(self.theme["main/defaultwindow/style"])
self.setWindowTitle("Pick a theme")
instructions = QtWidgets.QLabel("Pick a theme:")
avail_themes = config.availableThemes()
self.themeBox = QtWidgets.QComboBox(self)
for i, t in enumerate(avail_themes):
self.themeBox.addItem(t)
if t == theme.name:
self.themeBox.setCurrentIndex(i)
self.ok = QtWidgets.QPushButton("OK", self)
self.ok.setDefault(True)
self.ok.clicked.connect(self.accept)
self.cancel = QtWidgets.QPushButton("CANCEL", self)
self.cancel.clicked.connect(self.reject)
layout_ok = QtWidgets.QHBoxLayout()
layout_ok.addWidget(self.cancel)
layout_ok.addWidget(self.ok)
layout_0 = QtWidgets.QVBoxLayout()
layout_0.addWidget(instructions)
layout_0.addWidget(self.themeBox)
layout_0.addLayout(layout_ok)
self.setLayout(layout_0)
self.accepted.connect(parent.themeSelected)
self.rejected.connect(parent.closeTheme)
class PesterChooseProfile(QtWidgets.QDialog):
def __init__(
self, userprofile, config, theme, parent, collision=None, svsnick=None
):
QtWidgets.QDialog.__init__(self, parent)
self.userprofile = userprofile
self.theme = theme
self.config = config
self.parent = parent
self.setStyleSheet(self.theme["main/defaultwindow/style"])
self.currentHandle = QtWidgets.QLabel(
"CHANGING FROM %s" % userprofile.chat.handle
)
self.chumHandle = QtWidgets.QLineEdit(self)
self.chumHandle.setMinimumWidth(200)
self.chumHandle.setObjectName("setprofilehandle")
self.chumHandleLabel = QtWidgets.QLabel(
self.theme["main/mychumhandle/label/text"], self
)
self.chumColorButton = QtWidgets.QPushButton(self)
self.chumColorButton.setObjectName("setprofilecolor")
self.chumColorButton.resize(50, 20)
self.chumColorButton.setStyleSheet(
"background: %s" % (userprofile.chat.colorhtml())
)
self.chumcolor = userprofile.chat.color
self.chumColorButton.clicked.connect(self.openColorDialog)
layout_1 = QtWidgets.QHBoxLayout()
layout_1.addWidget(self.chumHandleLabel)
layout_1.addWidget(self.chumHandle)
layout_1.addWidget(self.chumColorButton)
# available profiles?
avail_profiles = self.config.availableProfiles()
if avail_profiles:
self.profileBox = QtWidgets.QComboBox(self)
self.profileBox.addItem("Choose a profile...")
for p in avail_profiles:
# PchumLog.debug("Adding profile: %s" % p.chat.handle)
self.profileBox.addItem(p.chat.handle)
else:
self.profileBox = None
self.defaultcheck = QtWidgets.QCheckBox(self)
self.defaultlabel = QtWidgets.QLabel("Set This Profile As Default", self)
layout_2 = QtWidgets.QHBoxLayout()
layout_2.addWidget(self.defaultlabel)
layout_2.addWidget(self.defaultcheck)
self.ok = QtWidgets.QPushButton("OK", self)
self.ok.setDefault(True)
self.ok.clicked.connect(self.validateProfile)
self.cancel = QtWidgets.QPushButton("CANCEL", self)
self.cancel.clicked.connect(self.reject)
if not collision and avail_profiles:
self.delete = QtWidgets.QPushButton("DELETE", self)
self.delete.clicked.connect(self.deleteProfile)
layout_ok = QtWidgets.QHBoxLayout()
layout_ok.addWidget(self.cancel)
layout_ok.addWidget(self.ok)
layout_0 = QtWidgets.QVBoxLayout()
if collision:
collision_warning = QtWidgets.QLabel(
"%s is taken already! Pick a new profile." % (collision)
)
layout_0.addWidget(collision_warning)
elif svsnick is not None:
svsnick_warning = QtWidgets.QLabel(
"Your handle got changed from %s to %s! Pick a new profile." % svsnick
)
layout_0.addWidget(svsnick_warning)
else:
layout_0.addWidget(
self.currentHandle, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter
)
layout_0.addLayout(layout_1)
if avail_profiles:
profileLabel = QtWidgets.QLabel("Or choose an existing profile:", self)
layout_0.addWidget(profileLabel)
layout_0.addWidget(self.profileBox)
layout_0.addLayout(layout_ok)
if not collision and avail_profiles:
layout_0.addWidget(self.delete)
layout_0.addLayout(layout_2)
self.errorMsg = QtWidgets.QLabel(self)
self.errorMsg.setObjectName("errormsg")
self.errorMsg.setStyleSheet("color:red;")
layout_0.addWidget(self.errorMsg)
self.setLayout(layout_0)
self.accepted.connect(parent.profileSelected)
self.rejected.connect(parent.closeProfile)
@QtCore.pyqtSlot()
def openColorDialog(self):
self.colorDialog = QtWidgets.QColorDialog(self)
color = self.colorDialog.getColor(initial=self.userprofile.chat.color)
self.chumColorButton.setStyleSheet("background: %s" % color.name())
self.chumcolor = color
self.colorDialog = None
@QtCore.pyqtSlot()
def validateProfile(self):
if not self.profileBox or self.profileBox.currentIndex() == 0:
handle = self.chumHandle.text()
if not PesterProfile.checkLength(handle):
self.errorMsg.setText("PROFILE HANDLE IS TOO LONG")
return
if not PesterProfile.checkValid(handle)[0]:
self.errorMsg.setText(
"NOT A VALID CHUMTAG. REASON:\n%s"
% (PesterProfile.checkValid(handle)[1])
)
return
self.accept()
@QtCore.pyqtSlot()
def deleteProfile(self):
if self.profileBox and self.profileBox.currentIndex() > 0:
handle = self.profileBox.currentText()
if handle == self.parent.profile().handle:
problem = QtWidgets.QMessageBox()
# karxi Will probably change this to its own name later.
problem.setObjectName("errmsg")
problem.setStyleSheet(self.theme["main/defaultwindow/style"])
problem.setWindowTitle("Problem!")
problem.setInformativeText(
"You can't delete the profile you're currently using!"
)
problem.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok)
problem.exec()
return
# TODO: Make this select 'no' as the default, as usual.
msgbox = QtWidgets.QMessageBox()
msgbox.setStyleSheet(self.theme["main/defaultwindow/style"])
msgbox.setWindowTitle("WARNING!")
msgbox.setInformativeText(
"Are you sure you want to delete the profile: %s" % (handle)
)
msgbox.setStandardButtons(
QtWidgets.QMessageBox.StandardButton.Ok
| QtWidgets.QMessageBox.StandardButton.Cancel
)
ret = msgbox.exec()
if ret == QtWidgets.QMessageBox.StandardButton.Ok:
try:
remove(_datadir + "profiles/%s.js" % (handle))
except OSError:
problem = QtWidgets.QMessageBox()
problem.setObjectName("errmsg")
problem.setStyleSheet(self.theme["main/defaultwindow/style"])
problem.setWindowTitle("Problem!")
problem.setInformativeText(
"There was a problem deleting the profile: %s" % (handle)
)
problem.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok)
problem.exec()
class PesterMentions(QtWidgets.QDialog):
def __init__(self, window, theme, parent):
QtWidgets.QDialog.__init__(self, parent)
self.setWindowTitle("Mentions")
self.setModal(True)
self.mainwindow = window
self.theme = theme
self.setStyleSheet(self.theme["main/defaultwindow/style"])
self.mentionlist = QtWidgets.QListWidget(self)
self.mentionlist.addItems(self.mainwindow.userprofile.getMentions())
self.addBtn = QtWidgets.QPushButton("ADD MENTION", self)
self.addBtn.clicked.connect(self.addMention)
self.editBtn = QtWidgets.QPushButton("EDIT", self)
self.editBtn.clicked.connect(self.editSelected)
self.rmBtn = QtWidgets.QPushButton("REMOVE", self)
self.rmBtn.clicked.connect(self.removeCurrent)
layout_1 = QtWidgets.QHBoxLayout()
layout_1.addWidget(self.editBtn)
layout_1.addWidget(self.rmBtn)
self.ok = QtWidgets.QPushButton("OK", self)
self.ok.setDefault(True)
self.ok.clicked.connect(self.accept)
self.cancel = QtWidgets.QPushButton("CANCEL", self)
self.cancel.clicked.connect(self.reject)
layout_2 = QtWidgets.QHBoxLayout()
layout_2.addWidget(self.cancel)
layout_2.addWidget(self.ok)
layout_0 = QtWidgets.QVBoxLayout()
layout_0.addWidget(self.mentionlist)
layout_0.addWidget(self.addBtn)
layout_0.addLayout(layout_1)
layout_0.addLayout(layout_2)
self.setLayout(layout_0)
@QtCore.pyqtSlot()
def editSelected(self):
m = self.mentionlist.currentItem()
if not m:
return
self.addMention(m)
@QtCore.pyqtSlot()
def addMention(self, mitem=None):
d = {"label": "Mention:", "inputname": "value"}
if mitem is not None:
d["value"] = mitem.text()
pdict = MultiTextDialog("ENTER MENTION", self, d).getText()
if pdict is None:
return
try:
re.compile(pdict["value"])
except re.error as e:
quirkWarning = QtWidgets.QMessageBox(self)
quirkWarning.setText("Not a valid regular expression!")
quirkWarning.setInformativeText("H3R3S WHY DUMP4SS: %s" % (e))
quirkWarning.exec()
else:
if mitem is None:
self.mentionlist.addItem(pdict["value"])
else:
mitem.setText(pdict["value"])
@QtCore.pyqtSlot()
def removeCurrent(self):
i = self.mentionlist.currentRow()
if i >= 0:
self.mentionlist.takeItem(i)
class PesterOptions(QtWidgets.QDialog):
def __init__(self, config, theme, parent):
QtWidgets.QDialog.__init__(self, parent)
self.setWindowTitle("Options")
self.setModal(False)
self.config = config
self.theme = theme
self.setStyleSheet(self.theme["main/defaultwindow/style"])
layout_4 = QtWidgets.QVBoxLayout()
hr = QtWidgets.QFrame()
hr.setFrameShape(QtWidgets.QFrame.Shape.HLine)
hr.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
vr = QtWidgets.QFrame()
vr.setFrameShape(QtWidgets.QFrame.Shape.VLine)
vr.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
self.tabs = QtWidgets.QButtonGroup(self)
self.tabs.buttonClicked.connect(self.changePage) # Verify working
self.tabNames = [
"Chum List",
"Conversations",
"Interface",
"Sound",
"Notifications",
"Logging",
"Idle/Updates",
"Theme",
"IRC",
]
if parent.advanced:
self.tabNames.append("Advanced")
for t in self.tabNames:
button = QtWidgets.QPushButton(t)
self.tabs.addButton(button)
layout_4.addWidget(button)
button.setCheckable(True)
self.tabs.button(-2).setChecked(True)
self.pages = QtWidgets.QStackedWidget(self)
self.irc_mode_check = QtWidgets.QCheckBox("IRC compatibility mode", self)
if self.config.irc_compatibility_mode():
self.irc_mode_check.setChecked(True)
bandwidthLabel = QtWidgets.QLabel(
"Enable this if you're planning on using Pesterchum on a server with normal IRC clients."
"\nStops the client from sending or requesting:"
"\n - Non-metadata moods (MOOD >0, GETMOOD, etc.)"
"\n - Non-metadata dm colors (COLOR >0,0,0)"
"\n - Memo message initials and color (<c=0,0,0>EB: </c>)"
"\n - Memo timelines"
"\n - Misc. PESTERCHUM:X commands (BEGIN, CEASE, BLOCK, IDLE, etc.)"
)
font = bandwidthLabel.font()
font.setPointSize(8)
bandwidthLabel.setFont(font)
self.force_prefix_check = QtWidgets.QCheckBox(
"Force all memo messages to have valid initials.", self
)
if self.config.force_prefix():
self.force_prefix_check.setChecked(True)
initials_label = QtWidgets.QLabel(
"Disable to allow users to send messages without initials, like Doc Scratch."
)
font = initials_label.font()
font.setPointSize(8)
initials_label.setFont(font)
self.autonickserv = QtWidgets.QCheckBox("Auto-Identify with NickServ", self)
self.autonickserv.setChecked(parent.userprofile.getAutoIdentify())
self.autonickserv.stateChanged[int].connect(self.autoNickServChange)
self.nickservpass = QtWidgets.QLineEdit(self)
self.nickservpass.setPlaceholderText("NickServ Password")
self.nickservpass.setEchoMode(QtWidgets.QLineEdit.EchoMode.PasswordEchoOnEdit)
self.nickservpass.setText(parent.userprofile.getNickServPass())
self.autojoinlist = QtWidgets.QListWidget(self)
self.autojoinlist.addItems(parent.userprofile.getAutoJoins())
self.addAutoJoinBtn = QtWidgets.QPushButton("Add", self)
self.addAutoJoinBtn.clicked.connect(self.addAutoJoin)
self.delAutoJoinBtn = QtWidgets.QPushButton("Remove", self)
self.delAutoJoinBtn.clicked.connect(self.delAutoJoin)
self.tabcheck = QtWidgets.QCheckBox("Tabbed Conversations", self)
if self.config.tabs():
self.tabcheck.setChecked(True)
self.tabmemocheck = QtWidgets.QCheckBox("Tabbed Memos", self)
if self.config.tabMemos():
self.tabmemocheck.setChecked(True)
self.hideOffline = QtWidgets.QCheckBox("Hide Offline Chums", self)
if self.config.hideOfflineChums():
self.hideOffline.setChecked(True)
self.soundcheck = QtWidgets.QCheckBox("Sounds On", self)
self.soundcheck.stateChanged[int].connect(self.soundChange)
self.chatsoundcheck = QtWidgets.QCheckBox("Pester Sounds", self)
self.chatsoundcheck.setChecked(self.config.chatSound())
self.memosoundcheck = QtWidgets.QCheckBox("Memo Sounds", self)
self.memosoundcheck.setChecked(self.config.memoSound())
self.memosoundcheck.stateChanged[int].connect(self.memoSoundChange)
self.memopingcheck = QtWidgets.QCheckBox("Memo Ping", self)
self.memopingcheck.setChecked(self.config.memoPing())
self.namesoundcheck = QtWidgets.QCheckBox("Memo Mention (initials)", self)
self.namesoundcheck.setChecked(self.config.nameSound())
if self.config.soundOn():
self.soundcheck.setChecked(True)
if not self.memosoundcheck.isChecked():
self.memoSoundChange(0)
else:
self.chatsoundcheck.setEnabled(False)
self.memosoundcheck.setEnabled(False)
self.memoSoundChange(0)
self.editMentions = QtWidgets.QPushButton("Edit Mentions", self)
self.editMentions.clicked.connect(self.openMentions)
self.editMentions2 = QtWidgets.QPushButton("Edit Mentions", self)
self.editMentions2.clicked.connect(self.openMentions)
self.volume = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal, self)
self.volume.setMinimum(0)
self.volume.setMaximum(100)
self.volume.setValue(self.config.volume())
self.volume.valueChanged[int].connect(self.printValue)
# Disable the volume slider if we can't actually use it.
if parent.canSetVolume():
self.currentVol = QtWidgets.QLabel(f"{self.config.volume()!s}%", self)
# We don't need to explicitly set this, but it helps drive the
# point home
self.volume.setEnabled(True)
else:
# We can't set the volume....
self.currentVol = QtWidgets.QLabel("(Disabled: Sound Mixer Error)", self)
self.volume.setEnabled(False)
self.currentVol.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter)
self.timestampcheck = QtWidgets.QCheckBox("Time Stamps", self)
if self.config.showTimeStamps():
self.timestampcheck.setChecked(True)
self.timestampBox = QtWidgets.QComboBox(self)
self.timestampBox.addItem("12 hour")
self.timestampBox.addItem("24 hour")
if self.config.time12Format():
self.timestampBox.setCurrentIndex(0)
else:
self.timestampBox.setCurrentIndex(1)
self.secondscheck = QtWidgets.QCheckBox("Show Seconds", self)
if self.config.showSeconds():
self.secondscheck.setChecked(True)
self.memomessagecheck = QtWidgets.QCheckBox(
"Show OP and Voice Messages in Memos", self
)
if self.config.opvoiceMessages():
self.memomessagecheck.setChecked(True)
if not ostools.isOSXBundle():
self.animationscheck = QtWidgets.QCheckBox("Use animated smilies", self)
if self.config.animations():
self.animationscheck.setChecked(True)
animateLabel = QtWidgets.QLabel(
"(Disable if you leave chats open for LOOOONG periods of time)"
)
font = animateLabel.font()
font.setPointSize(8)
animateLabel.setFont(font)
self.userlinkscheck = QtWidgets.QCheckBox("Disable #Memo and @User Links", self)
self.userlinkscheck.setChecked(self.config.disableUserLinks())
self.userlinkscheck.setVisible(False)
# Will add ability to turn off groups later
# self.groupscheck = QtGui.QCheckBox("Use Groups", self)
# self.groupscheck.setChecked(self.config.useGroups())
self.showemptycheck = QtWidgets.QCheckBox("Show Empty Groups", self)
self.showemptycheck.setChecked(self.config.showEmptyGroups())
self.showonlinenumbers = QtWidgets.QCheckBox(
"Show Number of Online Chums", self
)
self.showonlinenumbers.setChecked(self.config.showOnlineNumbers())
sortLabel = QtWidgets.QLabel("Sort Chums")
self.sortBox = QtWidgets.QComboBox(self)
self.sortBox.addItem("Alphabetically")
self.sortBox.addItem("By Mood")
self.sortBox.addItem("Manually")
method = self.config.sortMethod()
if method >= 0 and method < self.sortBox.count():
self.sortBox.setCurrentIndex(method)
layout_3 = QtWidgets.QHBoxLayout()
layout_3.addWidget(sortLabel)
layout_3.addWidget(self.sortBox, 10)
self.logpesterscheck = QtWidgets.QCheckBox("Log all Pesters", self)
if self.config.logPesters() & self.config.LOG:
self.logpesterscheck.setChecked(True)
self.logmemoscheck = QtWidgets.QCheckBox("Log all Memos", self)
if self.config.logMemos() & self.config.LOG:
self.logmemoscheck.setChecked(True)
self.stamppestercheck = QtWidgets.QCheckBox("Log Time Stamps for Pesters", self)
if self.config.logPesters() & self.config.STAMP:
self.stamppestercheck.setChecked(True)
self.stampmemocheck = QtWidgets.QCheckBox("Log Time Stamps for Memos", self)
if self.config.logMemos() & self.config.STAMP:
self.stampmemocheck.setChecked(True)
self.idleBox = QtWidgets.QSpinBox(self)
self.idleBox.setStyleSheet("background:#FFFFFF")
self.idleBox.setRange(1, 1440)
self.idleBox.setValue(self.config.idleTime())
layout_5 = QtWidgets.QHBoxLayout()
layout_5.addWidget(QtWidgets.QLabel("Minutes before Idle:"))
layout_5.addWidget(self.idleBox)
layout_repo_url = QtWidgets.QHBoxLayout()
self.repoUrlBox = QtWidgets.QLineEdit(self)
self.repoUrlBox.setText(self.config.theme_repo_url())
layout_repo_url.addWidget(QtWidgets.QLabel("Theme repository db URL:"))
layout_repo_url.addWidget(self.repoUrlBox)
# self.updateBox = QtWidgets.QComboBox(self)
# self.updateBox.addItem("Once a Day")
# self.updateBox.addItem("Once a Week")
# self.updateBox.addItem("Only on Start")
# self.updateBox.addItem("Never")
# check = self.config.checkForUpdates()
# if check >= 0 and check < self.updateBox.count():
# self.updateBox.setCurrentIndex(check)
layout_6 = QtWidgets.QHBoxLayout()
# layout_6.addWidget(QtWidgets.QLabel("Check for\nPesterchum Updates:"))
# layout_6.addWidget(self.updateBox)
# if not ostools.isOSXLeopard():
# self.mspaCheck = QtWidgets.QCheckBox("Check for MSPA Updates", self)
# self.mspaCheck.setChecked(self.config.checkMSPA())
self.randomscheck = QtWidgets.QCheckBox("Receive Random Encounters")
self.randomscheck.setChecked(parent.userprofile.randoms)
if not parent.randhandler.running:
self.randomscheck.setEnabled(False)
self.themeBox = QtWidgets.QComboBox(self)
def reset_themeBox():
avail_themes = self.config.availableThemes()
PchumLog.debug("Resetting themeself.themeBox")
self.themeBox.clear()
notheme = theme.name not in avail_themes
for i, t in enumerate(avail_themes):
self.themeBox.addItem(t)
if (not notheme and t == theme.name) or (notheme and t == "pesterchum"):
self.themeBox.setCurrentIndex(i)
self.themeBox.setSizePolicy(
QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
)
reset_themeBox()
self.refreshtheme = QtWidgets.QPushButton("Refresh current theme", self)
self.refreshtheme.clicked.connect(parent.themeSelectOverride)
self.refreshtheme.setSizePolicy(
QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Minimum,
)
)
self.themeManager = ThemeManagerWidget(self.config)
self.themeManager.rebuilt.connect(reset_themeBox)
# This makes it so that the themeBox gets updated when a theme is installed or removed through the repository
self.ghostchum = QtWidgets.QCheckBox("Pesterdunk Ghostchum!!", self)
self.ghostchum.setChecked(self.config.ghostchum())
self.buttonOptions = ["Minimize to Taskbar", "Minimize to Tray", "Quit"]
self.miniBox = QtWidgets.QComboBox(self)
self.miniBox.addItems(self.buttonOptions)
self.miniBox.setCurrentIndex(self.config.minimizeAction())
self.closeBox = QtWidgets.QComboBox(self)
self.closeBox.addItems(self.buttonOptions)
self.closeBox.setCurrentIndex(self.config.closeAction())
layout_mini = QtWidgets.QHBoxLayout()
layout_mini.addWidget(QtWidgets.QLabel("Minimize"))
layout_mini.addWidget(self.miniBox)
layout_close = QtWidgets.QHBoxLayout()
layout_close.addWidget(QtWidgets.QLabel("Close"))
layout_close.addWidget(self.closeBox)
self.pesterBlink = QtWidgets.QCheckBox("Blink Taskbar on Pesters", self)
if self.config.blink() & self.config.PBLINK:
self.pesterBlink.setChecked(True)
self.memoBlink = QtWidgets.QCheckBox("Blink Taskbar on Memos", self)
if self.config.blink() & self.config.MBLINK:
self.memoBlink.setChecked(True)
self.notifycheck = QtWidgets.QCheckBox("Toast Notifications", self)
if self.config.notify():
self.notifycheck.setChecked(True)
self.notifycheck.stateChanged[int].connect(self.notifyChange)
self.notifyOptions = QtWidgets.QComboBox(self)
types = self.parent().tm.availableTypes()
cur = self.parent().tm.currentType()
self.notifyOptions.addItems(types)
for i, t in enumerate(types):
if t == cur:
self.notifyOptions.setCurrentIndex(i)
break
self.notifyTypeLabel = QtWidgets.QLabel("Type", self)
layout_type = QtWidgets.QHBoxLayout()
layout_type.addWidget(self.notifyTypeLabel)
layout_type.addWidget(self.notifyOptions)
self.notifySigninCheck = QtWidgets.QCheckBox("Chum signs in", self)
if self.config.notifyOptions() & self.config.SIGNIN:
self.notifySigninCheck.setChecked(True)
self.notifySignoutCheck = QtWidgets.QCheckBox("Chum signs out", self)
if self.config.notifyOptions() & self.config.SIGNOUT:
self.notifySignoutCheck.setChecked(True)
self.notifyNewMsgCheck = QtWidgets.QCheckBox("New messages", self)
if self.config.notifyOptions() & self.config.NEWMSG:
self.notifyNewMsgCheck.setChecked(True)
self.notifyNewConvoCheck = QtWidgets.QCheckBox("Only new conversations", self)
if self.config.notifyOptions() & self.config.NEWCONVO:
self.notifyNewConvoCheck.setChecked(True)
self.notifyMentionsCheck = QtWidgets.QCheckBox("Memo Mentions (initials)", self)
if self.config.notifyOptions() & self.config.INITIALS:
self.notifyMentionsCheck.setChecked(True)
self.notifyChange(self.notifycheck.checkState())
if parent.advanced:
# NOTE: This doesn't do anything right now - so change it!
self.modechange = QtWidgets.QLineEdit(self)
layout_change = QtWidgets.QHBoxLayout()
layout_change.addWidget(QtWidgets.QLabel("Change:"))
layout_change.addWidget(self.modechange)
self.ok = QtWidgets.QPushButton("OK", self)
self.ok.setDefault(True)
self.ok.clicked.connect(self.accept)
self.cancel = QtWidgets.QPushButton("CANCEL", self)
self.cancel.clicked.connect(self.reject)
layout_2 = QtWidgets.QHBoxLayout()
layout_2.addWidget(self.cancel)
layout_2.addWidget(self.ok)
# Tab layouts
# Chum List
widget = QtWidgets.QWidget()
layout_chumlist = QtWidgets.QVBoxLayout(widget)
layout_chumlist.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_chumlist.addWidget(self.hideOffline)
# layout_chumlist.addWidget(self.groupscheck)
layout_chumlist.addWidget(self.showemptycheck)
layout_chumlist.addWidget(self.showonlinenumbers)
layout_chumlist.addLayout(layout_3)
self.pages.addWidget(widget)
# Conversations
widget = QtWidgets.QWidget()
layout_chat = QtWidgets.QVBoxLayout(widget)
layout_chat.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_chat.addWidget(self.timestampcheck)
layout_chat.addWidget(self.timestampBox)
layout_chat.addWidget(self.secondscheck)
layout_chat.addWidget(self.memomessagecheck)
if not ostools.isOSXBundle():
layout_chat.addWidget(self.animationscheck)
layout_chat.addWidget(animateLabel)
layout_chat.addWidget(self.randomscheck)
# Re-enable these when it's possible to disable User and Memo links
# layout_chat.addWidget(hr)
# layout_chat.addWidget(QtGui.QLabel("User and Memo Links"))
# layout_chat.addWidget(self.userlinkscheck)
self.pages.addWidget(widget)
# Interface
widget = QtWidgets.QWidget()
layout_interface = QtWidgets.QVBoxLayout(widget)
layout_interface.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_interface.addWidget(self.tabcheck)
layout_interface.addWidget(self.tabmemocheck)
layout_interface.addLayout(layout_mini)
layout_interface.addLayout(layout_close)
layout_interface.addWidget(self.pesterBlink)
layout_interface.addWidget(self.memoBlink)
self.pages.addWidget(widget)
# Sound
widget = QtWidgets.QWidget()
# Choose audio device
audioDeviceLabel = QtWidgets.QLabel("Audio output device:")
self.audioDeviceBox = QtWidgets.QComboBox(self)
current_audio_device = self.config.audioDevice()
active_index = None
if hasattr(QtMultimedia, "QMediaDevices"):
# PyQt6
try:
for i, output in enumerate(QtMultimedia.QMediaDevices.audioOutputs()):
self.audioDeviceBox.addItem(f"{output.description()}", output.id())
if output.id() == current_audio_device:
active_index = i
if active_index is not None:
self.audioDeviceBox.setCurrentIndex(active_index)
except AttributeError:
PchumLog.warning(
"Can't get audio devices, not using PyQt6 QtMultimedia?"
)
layout_sound = QtWidgets.QVBoxLayout(widget)
layout_sound.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_sound.addWidget(self.soundcheck)
layout_indent = QtWidgets.QVBoxLayout()
layout_indent.addWidget(self.chatsoundcheck)
layout_indent.addWidget(self.memosoundcheck)
layout_doubleindent = QtWidgets.QVBoxLayout()
layout_doubleindent.addWidget(self.memopingcheck)
layout_doubleindent.addWidget(self.namesoundcheck)
layout_doubleindent.addWidget(self.editMentions)
layout_doubleindent.setContentsMargins(22, 0, 0, 0)
layout_indent.addLayout(layout_doubleindent)
layout_indent.setContentsMargins(22, 0, 0, 0)
layout_sound.addLayout(layout_indent)
layout_sound.addSpacing(15)
layout_audioDevice = QtWidgets.QHBoxLayout()
layout_audioDevice.addWidget(audioDeviceLabel)
layout_audioDevice.addWidget(self.audioDeviceBox)
layout_sound.addLayout(layout_audioDevice)
mvol = QtWidgets.QLabel("Master Volume:", self)
# If we can't set the volume, grey this out as well
# ~mvol.setEnabled(parent.canSetVolume())
# Normally we'd grey this out, but that presently makes things
# rather unreadable
# Later we can look into changing the color to a theme[] entry
layout_sound.addWidget(mvol)
layout_sound.addWidget(self.volume)
layout_sound.addWidget(self.currentVol)
self.pages.addWidget(widget)
# Notifications
widget = QtWidgets.QWidget()
layout_notify = QtWidgets.QVBoxLayout(widget)
layout_notify.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_notify.addWidget(self.notifycheck)
layout_indent = QtWidgets.QVBoxLayout()
layout_indent.addLayout(layout_type)
layout_indent.setContentsMargins(22, 0, 0, 0)
layout_indent.addWidget(self.notifySigninCheck)
layout_indent.addWidget(self.notifySignoutCheck)
layout_indent.addWidget(self.notifyNewMsgCheck)
layout_doubleindent = QtWidgets.QVBoxLayout()
layout_doubleindent.addWidget(self.notifyNewConvoCheck)
layout_doubleindent.setContentsMargins(22, 0, 0, 0)
layout_indent.addLayout(layout_doubleindent)
layout_indent.addWidget(self.notifyMentionsCheck)
layout_indent.addWidget(self.editMentions2)
layout_notify.addLayout(layout_indent)
self.pages.addWidget(widget)
# Logging
widget = QtWidgets.QWidget()
layout_logs = QtWidgets.QVBoxLayout(widget)
layout_logs.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_logs.addWidget(self.logpesterscheck)
layout_logs.addWidget(self.logmemoscheck)
layout_logs.addWidget(self.stamppestercheck)
layout_logs.addWidget(self.stampmemocheck)
self.pages.addWidget(widget)
# Idle/Updates
widget = QtWidgets.QWidget()
layout_idle = QtWidgets.QVBoxLayout(widget)
layout_idle.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_idle.addLayout(layout_5)
layout_idle.addLayout(layout_repo_url)
layout_idle.addLayout(layout_6)
# if not ostools.isOSXLeopard():
# layout_idle.addWidget(self.mspaCheck)
self.pages.addWidget(widget)
# Theme
widget = QtWidgets.QWidget()
layout_theme = QtWidgets.QVBoxLayout(widget)
layout_theme.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_theme.addWidget(QtWidgets.QLabel("Pick a Theme:"))
layout_theme_hbox = QtWidgets.QHBoxLayout()
layout_theme_hbox.addWidget(self.themeBox)
layout_theme_hbox.addWidget(self.refreshtheme)
layout_theme.addLayout(layout_theme_hbox)
layout_theme.addWidget(QtWidgets.QLabel("Get new themes:"))
layout_theme.addWidget(self.themeManager)
layout_theme.addWidget(self.ghostchum)
self.pages.addWidget(widget)
# Connection
widget = QtWidgets.QWidget()
layout_connect = QtWidgets.QVBoxLayout(widget)
layout_connect.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_connect.addWidget(self.irc_mode_check)
layout_connect.addWidget(bandwidthLabel)
layout_connect.addWidget(self.force_prefix_check)
layout_connect.addWidget(initials_label)
layout_connect.addWidget(self.autonickserv)
layout_indent = QtWidgets.QVBoxLayout()
layout_indent.addWidget(self.nickservpass)
layout_indent.setContentsMargins(22, 0, 0, 0)
layout_connect.addLayout(layout_indent)
layout_connect.addWidget(QtWidgets.QLabel("Auto-Join Memos:"))
layout_connect.addWidget(self.autojoinlist)
layout_8 = QtWidgets.QHBoxLayout()
layout_8.addWidget(self.addAutoJoinBtn)
layout_8.addWidget(self.delAutoJoinBtn)
layout_connect.addLayout(layout_8)
self.pages.addWidget(widget)
# Advanced
if parent.advanced:
widget = QtWidgets.QWidget()
layout_advanced = QtWidgets.QVBoxLayout(widget)
layout_advanced.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_advanced.addWidget(
QtWidgets.QLabel("Current User Mode: %s" % parent.modes)
)
layout_advanced.addLayout(layout_change)
self.pages.addWidget(widget)
layout_0 = QtWidgets.QVBoxLayout()
layout_1 = QtWidgets.QHBoxLayout()
layout_1.addLayout(layout_4)
layout_1.addWidget(vr)
layout_1.addWidget(self.pages)
layout_0.addLayout(layout_1)
layout_0.addSpacing(30)
layout_0.addLayout(layout_2)
self.setLayout(layout_0)
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
def changePage(self, button):
self.pages.setCurrentIndex(self.tabNames.index(button.text()))
@QtCore.pyqtSlot(int)
def notifyChange(self, state):
if state == 0:
self.notifyTypeLabel.setEnabled(False)
self.notifyOptions.setEnabled(False)
self.notifySigninCheck.setEnabled(False)
self.notifySignoutCheck.setEnabled(False)
self.notifyNewMsgCheck.setEnabled(False)
self.notifyNewConvoCheck.setEnabled(False)
self.notifyMentionsCheck.setEnabled(False)
else:
self.notifyTypeLabel.setEnabled(True)
self.notifyOptions.setEnabled(True)
self.notifySigninCheck.setEnabled(True)
self.notifySignoutCheck.setEnabled(True)
self.notifyNewMsgCheck.setEnabled(True)
self.notifyNewConvoCheck.setEnabled(True)
self.notifyMentionsCheck.setEnabled(True)
@QtCore.pyqtSlot(int)
def autoNickServChange(self, state):
self.nickservpass.setEnabled(state != 0)
@QtCore.pyqtSlot()
def addAutoJoin(self, mitem=None):
d = {"label": "Memo:", "inputname": "value"}
if mitem is not None:
d["value"] = mitem.text()
pdict = MultiTextDialog("ENTER MEMO", self, d).getText()
if pdict is None:
return
pdict["value"] = "#" + pdict["value"]
if mitem is None:
items = self.autojoinlist.findItems(
pdict["value"], QtCore.Qt.MatchFlag.MatchFixedString
)
if len(items) == 0:
self.autojoinlist.addItem(pdict["value"])
else:
mitem.setText(pdict["value"])
@QtCore.pyqtSlot()
def delAutoJoin(self):
i = self.autojoinlist.currentRow()
if i >= 0:
self.autojoinlist.takeItem(i)
@QtCore.pyqtSlot(int)
def soundChange(self, state):
if state == 0:
self.chatsoundcheck.setEnabled(False)
self.memosoundcheck.setEnabled(False)
self.memoSoundChange(0)
else:
self.chatsoundcheck.setEnabled(True)
self.memosoundcheck.setEnabled(True)
if self.memosoundcheck.isChecked():
self.memoSoundChange(1)
@QtCore.pyqtSlot(int)
def memoSoundChange(self, state):
if state == 0:
self.memopingcheck.setEnabled(False)
self.namesoundcheck.setEnabled(False)
else:
self.memopingcheck.setEnabled(True)
self.namesoundcheck.setEnabled(True)
@QtCore.pyqtSlot(int)
def printValue(self, v):
self.currentVol.setText(f"{v}%")
@QtCore.pyqtSlot()
def openMentions(self):
if not hasattr(self, "mentionmenu"):
self.mentionmenu = None
if not self.mentionmenu:
self.mentionmenu = PesterMentions(self.parent(), self.theme, self)
self.mentionmenu.accepted.connect(self.updateMentions)
self.mentionmenu.rejected.connect(self.closeMentions)
self.mentionmenu.show()
self.mentionmenu.raise_()
self.mentionmenu.activateWindow()
@QtCore.pyqtSlot()
def closeMentions(self):
self.mentionmenu.close()
self.mentionmenu = None
@QtCore.pyqtSlot()
def updateMentions(self):
m = []
for i in range(self.mentionmenu.mentionlist.count()):
m.append((self.mentionmenu.mentionlist.item(i).text()))
self.parent().userprofile.setMentions(m)
self.mentionmenu = None
class PesterUserlist(QtWidgets.QDialog):
def __init__(self, config, theme, parent):
QtWidgets.QDialog.__init__(self, parent)
self.setModal(False)
self.config = config
self.theme = theme
self.mainwindow = parent
self.setStyleSheet(self.theme["main/defaultwindow/style"])
self.resize(200, 600)
self.searchbox = QtWidgets.QLineEdit(self)
# self.searchbox.setStyleSheet(theme["convo/input/style"]) # which style is better?
self.searchbox.setPlaceholderText("Search")
self.searchbox.textChanged[str].connect(self.updateUsers)
self.label = QtWidgets.QLabel("USERLIST")
self.userarea = RightClickList(self)
self.userarea.setStyleSheet(self.theme["main/chums/style"])
self.userarea.optionsMenu = QtWidgets.QMenu(self)
self.addChumAction = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/addchum"], self
)
self.addChumAction.triggered.connect(self.addChumSlot)
self.pesterChumAction = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/pester"], self
)
self.pesterChumAction.triggered.connect(self.pesterChumSlot)
self.userarea.optionsMenu.addAction(self.addChumAction)
self.userarea.optionsMenu.addAction(self.pesterChumAction)
self.ok = QtWidgets.QPushButton("OK", self)
self.ok.setDefault(True)
self.ok.clicked.connect(self.accept)
layout_0 = QtWidgets.QVBoxLayout()
layout_0.addWidget(self.label)
layout_0.addWidget(self.userarea)
layout_0.addWidget(self.searchbox)
layout_0.addWidget(self.ok)
self.setLayout(layout_0)
self.mainwindow.namesUpdated.connect(self.updateUsers)
self.mainwindow.userPresentSignal[str, str, str].connect(self.updateUserPresent)
self.updateUsers()
self.searchbox.setFocus()
@QtCore.pyqtSlot()
def updateUsers(self):
try:
names = self.mainwindow.namesdb["#pesterchum"]
except KeyError:
# Not in #pesterchum?
return
self.userarea.clear()
for n in names:
if (self.searchbox.text()) == "" or n.lower().find(
self.searchbox.text().lower()
) != -1:
# Strip channel membership prefixes
n = n.strip("~").strip("@").strip("+").strip("&").strip("%")
item = QtWidgets.QListWidgetItem(n)
item.setForeground(
QtGui.QBrush(QtGui.QColor(self.theme["main/chums/userlistcolor"]))
)
self.userarea.addItem(item)
self.userarea.sortItems()
@QtCore.pyqtSlot(str, str, str)
def updateUserPresent(self, handle, channel, update):
if update == "quit":
self.delUser(handle)
elif update == "left" and channel == "#pesterchum":
self.delUser(handle)
elif update == "join" and channel == "#pesterchum":
if (
self.searchbox.text() == ""
or handle.lower().find(self.searchbox.text().lower()) != -1
):
self.addUser(handle)
def addUser(self, name):
item = QtWidgets.QListWidgetItem(name)
item.setForeground(
QtGui.QBrush(QtGui.QColor(self.theme["main/chums/userlistcolor"]))
)
self.userarea.addItem(item)
self.userarea.sortItems()
def delUser(self, name):
matches = self.userarea.findItems(name, QtCore.Qt.MatchFlag.MatchExactly)
for m in matches:
self.userarea.takeItem(self.userarea.row(m))
def changeTheme(self, theme):
self.theme = theme
self.setStyleSheet(theme["main/defaultwindow/style"])
self.userarea.setStyleSheet(theme["main/chums/style"])
self.addChumAction.setText(theme["main/menus/rclickchumlist/addchum"])
for item in [self.userarea.item(i) for i in range(0, self.userarea.count())]:
item.setForeground(
0, QtGui.QBrush(QtGui.QColor(theme["main/chums/userlistcolor"]))
)
@QtCore.pyqtSlot()
def addChumSlot(self):
cur = self.userarea.currentItem()
if not cur:
return
self.addChum.emit(cur.text())
@QtCore.pyqtSlot()
def pesterChumSlot(self):
cur = self.userarea.currentItem()
if not cur:
return
self.pesterChum.emit(cur.text())
addChum = QtCore.pyqtSignal(str)
pesterChum = QtCore.pyqtSignal(str)
class MemoListItem(QtWidgets.QTreeWidgetItem):
def __init__(self, channel, usercount):
QtWidgets.QTreeWidgetItem.__init__(self, [channel, str(usercount)])
self.target = channel
def __lt__(self, other):
column = self.treeWidget().sortColumn()
if (self.text(column)).isdigit() and (other.text(column)).isdigit():
return int(self.text(column)) < int(other.text(column))
return self.text(column) < other.text(column)
class PesterMemoList(QtWidgets.QDialog):
def __init__(self, parent, channel=""):
QtWidgets.QDialog.__init__(self, parent)
self.setModal(False)
self.theme = parent.theme
self.mainwindow = parent
self.setStyleSheet(self.theme["main/defaultwindow/style"])
self.resize(460, 300)
self.label = QtWidgets.QLabel("MEMOS")
self.channelarea = RightClickTree(self)
self.channelarea.setSelectionMode(
QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection
)
self.channelarea.setStyleSheet(self.theme["main/chums/style"])
self.channelarea.optionsMenu = QtWidgets.QMenu(self)
self.channelarea.setColumnCount(2)
self.channelarea.setHeaderLabels(["Memo", "Users"])
self.channelarea.setIndentation(0)
self.channelarea.setColumnWidth(0, 200)
self.channelarea.setColumnWidth(1, 10)
self.channelarea.setSortingEnabled(True)
self.channelarea.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder)
self.channelarea.itemDoubleClicked.connect( # [QtWidgets.QTreeWidgetItem, int]
self.AcceptSelection
)
self.orjoinlabel = QtWidgets.QLabel("OR MAKE A NEW MEMO:")
self.newmemo = QtWidgets.QLineEdit(channel, self)
self.secretChannel = QtWidgets.QCheckBox("HIDDEN CHANNEL?", self)
self.inviteChannel = QtWidgets.QCheckBox("INVITATION ONLY?", self)
self.timelabel = QtWidgets.QLabel("TIMEFRAME:")
self.timeslider = TimeSlider(QtCore.Qt.Orientation.Horizontal, self)
self.timeinput = TimeInput(self.timeslider, self)
self.cancel = QtWidgets.QPushButton("CANCEL", self)
self.cancel.clicked.connect(self.reject)
self.join = QtWidgets.QPushButton("JOIN", self)
self.join.setDefault(True)
self.join.clicked.connect(self.AcceptIfSelectionMade)
layout_ok = QtWidgets.QHBoxLayout()
layout_ok.addWidget(self.cancel)
layout_ok.addWidget(self.join)
layout_left = QtWidgets.QVBoxLayout()
layout_right = QtWidgets.QVBoxLayout()
layout_right.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_0 = QtWidgets.QVBoxLayout()
layout_1 = QtWidgets.QHBoxLayout()
layout_left.addWidget(self.label)
layout_left.addWidget(self.channelarea)
layout_right.addWidget(self.orjoinlabel)
layout_right.addWidget(self.newmemo)
layout_right.addWidget(self.secretChannel)
layout_right.addWidget(self.inviteChannel)
layout_right.addWidget(self.timelabel)
layout_right.addWidget(self.timeslider)
layout_right.addWidget(self.timeinput)
layout_1.addLayout(layout_left)
layout_1.addLayout(layout_right)
layout_0.addLayout(layout_1)
layout_0.addLayout(layout_ok)
self.setLayout(layout_0)
def newmemoname(self):
return self.newmemo.text()
def SelectedMemos(self):
return self.channelarea.selectedItems()
def HasSelection(self):
return len(self.SelectedMemos()) > 0 or self.newmemoname()
def updateChannels(self, channels):
for c in channels:
item = MemoListItem(c[0][1:], c[1])
item.setForeground(
0, QtGui.QBrush(QtGui.QColor(self.theme["main/chums/userlistcolor"]))
)
item.setForeground(
1, QtGui.QBrush(QtGui.QColor(self.theme["main/chums/userlistcolor"]))
)
item.setIcon(0, QtGui.QIcon(self.theme["memos/memoicon"]))
self.channelarea.addTopLevelItem(item)
def updateTheme(self, theme):
self.theme = theme
self.setStyleSheet(theme["main/defaultwindow/style"])
for item in [self.userarea.item(i) for i in range(0, self.channelarea.count())]:
item.setForeground(
0, QtGui.QBrush(QtGui.QColor(theme["main/chums/userlistcolor"]))
)
item.setIcon(QtGui.QIcon(theme["memos/memoicon"]))
@QtCore.pyqtSlot()
def AcceptIfSelectionMade(self):
if self.HasSelection():
self.AcceptSelection()
@QtCore.pyqtSlot()
def AcceptSelection(self):
self.accept()
class LoadingScreen(QtWidgets.QDialog):
tryAgain = QtCore.pyqtSignal()
def __init__(self, parent=None):
QtWidgets.QDialog.__init__(
self,
parent,
QtCore.Qt.WindowType.CustomizeWindowHint
| QtCore.Qt.WindowType.FramelessWindowHint,
)
self.mainwindow = parent
self.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"])
# self.setWindowModality(QtCore.Qt.WindowModality.NonModal) # useless
# self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose) # useless
self.loadinglabel = QtWidgets.QLabel("CONN3CT1NG", self)
# self.loadinglabel.setTextFormat(QtCore.Qt.TextFormat.RichText) # Clickable html links
# self.loadinglabel.setWordWrap(True) # Unusable because of QT clipping bug (QTBUG-92381)
self.cancel = QtWidgets.QPushButton("QU1T >:?", self)
self.ok = QtWidgets.QPushButton("R3CONN3CT >:]", self)
# Help reduce the number of accidental Pesterchum closures... :|
self.cancel.setAutoDefault(False)
self.ok.setAutoDefault(True)
self.cancel.clicked.connect(self.reject)
self.ok.clicked.connect(self.tryAgain)
# self.finished.connect(self.finishedEvent)
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.loadinglabel)
layout_1 = QtWidgets.QHBoxLayout()
layout_1.addWidget(self.cancel)
layout_1.addWidget(self.ok)
self.layout.addLayout(layout_1)
self.setLayout(self.layout)
# Help reduce the number of accidental Pesterchum closures... :|
self.cancel.setDefault(False)
self.ok.setDefault(True)
self.ok.setFocus()
self.timer = None
# def finishedEvent(self, result):
# self.close()
def hideReconnect(self, safe=True):
self.ok.hide()
if safe:
# Set a timer so that we don't immediately disconnect anyway.
self.cancel.setEnabled(False)
# A few seconds should be enough.
self.timer = QtCore.QTimer.singleShot(2000, self.enableQuit)
def showReconnect(self):
self.ok.show()
# Again...stop accidental closes.
self.ok.setFocus()
@QtCore.pyqtSlot()
def enableQuit(self):
self.cancel.setEnabled(True)
class AboutPesterchum(QtWidgets.QDialog):
def __init__(self, parent=None):
QtWidgets.QDialog.__init__(self, parent)
self.mainwindow = parent
self.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"])
self.title = QtWidgets.QLabel("P3ST3RCHUM %s" % (_pcVersion))
self.credits = QtWidgets.QLabel(
"Programming by:"
"\n illuminatedwax (ghostDunk)"
"\n Kiooeht (evacipatedBox)"
"\n Lexi (lexicalNuance)"
"\n oakwhiz"
"\n alGore"
"\n Cerxi (binaryCabalist)"
"\n Arcane (arcaneAgilmente)"
"\n karxi (Midna)"
"\n Shou/Dpeta 🐱"
"\n"
"\nArt by:"
"\n Grimlive (aquaMarinist)"
"\n Cerxi (binaryCabalist)"
"\n cubicSimulation"
"\n"
"\nSpecial Thanks:"
"\n ABT"
"\n gamblingGenocider"
"\n Eco-Mono"
)
self.ok = QtWidgets.QPushButton("OK", self)
self.ok.clicked.connect(self.reject)
layout_0 = QtWidgets.QVBoxLayout()
layout_0.addWidget(self.title)
layout_0.addWidget(self.credits)
layout_0.addWidget(self.ok)
self.setLayout(layout_0)
class UpdatePesterchum(QtWidgets.QDialog):
def __init__(self, ver, url, parent=None):
QtWidgets.QDialog.__init__(self, parent)
self.url = url
self.mainwindow = parent
self.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"])
self.setWindowTitle("Pesterchum v%s Update" % (ver))
self.setModal(False)
self.title = QtWidgets.QLabel("An update to Pesterchum is available!")
layout_0 = QtWidgets.QVBoxLayout()
layout_0.addWidget(self.title)
self.ok = QtWidgets.QPushButton("D0WNL04D 4ND 1NST4LL N0W", self)
self.ok.setDefault(True)
self.ok.clicked.connect(self.accept)
self.cancel = QtWidgets.QPushButton("CANCEL", self)
self.cancel.clicked.connect(self.reject)
layout_2 = QtWidgets.QHBoxLayout()
layout_2.addWidget(self.cancel)
layout_2.addWidget(self.ok)
layout_0.addLayout(layout_2)
self.setLayout(layout_0)
class AddChumDialog(QtWidgets.QDialog):
def __init__(self, avail_groups, parent=None):
QtWidgets.QDialog.__init__(self, parent)
self.mainwindow = parent
self.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"])
self.setWindowTitle("Enter Chum Handle")
self.setModal(True)
self.title = QtWidgets.QLabel("Enter Chum Handle")
self.chumBox = QtWidgets.QLineEdit(self)
self.groupBox = QtWidgets.QComboBox(self)
avail_groups.sort()
avail_groups.pop(avail_groups.index("Chums"))
avail_groups.insert(0, "Chums")
for g in avail_groups:
self.groupBox.addItem(g)
self.newgrouplabel = QtWidgets.QLabel("Or make a new group:")
self.newgroup = QtWidgets.QLineEdit(self)
layout_0 = QtWidgets.QVBoxLayout()
layout_0.addWidget(self.title)
layout_0.addWidget(self.chumBox)
layout_0.addWidget(self.groupBox)
layout_0.addWidget(self.newgrouplabel)
layout_0.addWidget(self.newgroup)
self.ok = QtWidgets.QPushButton("OK", self)
self.ok.setDefault(True)
self.ok.clicked.connect(self.accept)
self.cancel = QtWidgets.QPushButton("CANCEL", self)
self.cancel.clicked.connect(self.reject)
layout_2 = QtWidgets.QHBoxLayout()
layout_2.addWidget(self.cancel)
layout_2.addWidget(self.ok)
layout_0.addLayout(layout_2)
self.setLayout(layout_0)