2019-09-10 00:54:28 +00:00
|
|
|
# Created By: Virgil Dupras
|
|
|
|
# Created On: 2011-02-01
|
|
|
|
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
|
|
#
|
|
|
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
|
|
# which should be included with this package. The terms are also available at
|
|
|
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import io
|
|
|
|
import os.path as op
|
|
|
|
import os
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from hscommon.util import first
|
|
|
|
|
|
|
|
from PyQt5.QtCore import QStandardPaths
|
|
|
|
from PyQt5.QtGui import QPixmap, QIcon
|
2020-01-01 02:16:27 +00:00
|
|
|
from PyQt5.QtWidgets import (
|
|
|
|
QDesktopWidget,
|
|
|
|
QSpacerItem,
|
|
|
|
QSizePolicy,
|
|
|
|
QAction,
|
|
|
|
QHBoxLayout,
|
|
|
|
)
|
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
|
|
|
|
def moveToScreenCenter(widget):
|
|
|
|
frame = widget.frameGeometry()
|
|
|
|
frame.moveCenter(QDesktopWidget().availableGeometry().center())
|
|
|
|
widget.move(frame.topLeft())
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def verticalSpacer(size=None):
|
|
|
|
if size:
|
|
|
|
return QSpacerItem(1, size, QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
|
|
else:
|
|
|
|
return QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def horizontalSpacer(size=None):
|
|
|
|
if size:
|
|
|
|
return QSpacerItem(size, 1, QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
|
|
else:
|
|
|
|
return QSpacerItem(1, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def horizontalWrap(widgets):
|
|
|
|
"""Wrap all widgets in `widgets` in a horizontal layout.
|
|
|
|
|
|
|
|
If, instead of placing a widget in your list, you place an int or None, an horizontal spacer
|
|
|
|
with the width corresponding to the int will be placed (0 or None means an expanding spacer).
|
|
|
|
"""
|
|
|
|
layout = QHBoxLayout()
|
|
|
|
for widget in widgets:
|
|
|
|
if widget is None or isinstance(widget, int):
|
|
|
|
layout.addItem(horizontalSpacer(size=widget))
|
|
|
|
else:
|
|
|
|
layout.addWidget(widget)
|
|
|
|
return layout
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def createActions(actions, target):
|
|
|
|
# actions = [(name, shortcut, icon, desc, func)]
|
|
|
|
for name, shortcut, icon, desc, func in actions:
|
|
|
|
action = QAction(target)
|
|
|
|
if icon:
|
2020-01-01 02:16:27 +00:00
|
|
|
action.setIcon(QIcon(QPixmap(":/" + icon)))
|
2019-09-10 00:54:28 +00:00
|
|
|
if shortcut:
|
|
|
|
action.setShortcut(shortcut)
|
|
|
|
action.setText(desc)
|
|
|
|
action.triggered.connect(func)
|
|
|
|
setattr(target, name, action)
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def setAccelKeys(menu):
|
|
|
|
actions = menu.actions()
|
|
|
|
titles = [a.text() for a in actions]
|
|
|
|
available_characters = {c.lower() for s in titles for c in s if c.isalpha()}
|
|
|
|
for action in actions:
|
|
|
|
text = action.text()
|
|
|
|
c = first(c for c in text if c.lower() in available_characters)
|
|
|
|
if c is None:
|
|
|
|
continue
|
|
|
|
i = text.index(c)
|
2020-01-01 02:16:27 +00:00
|
|
|
newtext = text[:i] + "&" + text[i:]
|
2019-09-10 00:54:28 +00:00
|
|
|
available_characters.remove(c.lower())
|
|
|
|
action.setText(newtext)
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def getAppData():
|
|
|
|
return QStandardPaths.standardLocations(QStandardPaths.DataLocation)[0]
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
class SysWrapper(io.IOBase):
|
|
|
|
def write(self, s):
|
2020-01-01 02:16:27 +00:00
|
|
|
if s.strip(): # don't log empty stuff
|
2019-09-10 00:54:28 +00:00
|
|
|
logging.warning(s)
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def setupQtLogging(level=logging.WARNING, log_to_stdout=False):
|
|
|
|
# Under Qt, we log in "debug.log" in appdata. Moreover, when under cx_freeze, we have a
|
|
|
|
# problem because sys.stdout and sys.stderr are None, so we need to replace them with a
|
|
|
|
# wrapper that logs with the logging module.
|
|
|
|
appdata = getAppData()
|
|
|
|
if not op.exists(appdata):
|
|
|
|
os.makedirs(appdata)
|
2020-01-01 02:16:27 +00:00
|
|
|
# Setup logging
|
2019-09-10 00:54:28 +00:00
|
|
|
# Have to use full configuration over basicConfig as FileHandler encoding was not being set.
|
2020-01-01 02:16:27 +00:00
|
|
|
filename = op.join(appdata, "debug.log") if not log_to_stdout else None
|
2019-09-10 00:54:28 +00:00
|
|
|
log = logging.getLogger()
|
2020-01-01 02:16:27 +00:00
|
|
|
handler = logging.FileHandler(filename, "a", "utf-8")
|
|
|
|
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
2019-09-10 00:54:28 +00:00
|
|
|
handler.setFormatter(formatter)
|
|
|
|
log.addHandler(handler)
|
2020-01-01 02:16:27 +00:00
|
|
|
if sys.stderr is None: # happens under a cx_freeze environment
|
2019-09-10 00:54:28 +00:00
|
|
|
sys.stderr = SysWrapper()
|
|
|
|
if sys.stdout is None:
|
|
|
|
sys.stdout = SysWrapper()
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def escapeamp(s):
|
|
|
|
# Returns `s` with escaped ampersand (& --> &&). QAction text needs to have & escaped because
|
|
|
|
# that character is used to define "accel keys".
|
2020-01-01 02:16:27 +00:00
|
|
|
return s.replace("&", "&&")
|