manuskript/manuskript/settingsWindow.py
TheJackiMonster 98d6eb4975
Remove usage of hardcoded path separators
Signed-off-by: TheJackiMonster <thejackimonster@gmail.com>
2023-12-12 15:53:37 +01:00

1003 lines
46 KiB
Python

#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
import shutil
from collections import OrderedDict
from PyQt5.QtCore import QSize, QSettings, QRegExp, QTranslator, QObject
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIntValidator, QIcon, QFont, QColor, QPixmap, QStandardItem, QPainter
from PyQt5.QtGui import QStyleHints
from PyQt5.QtWidgets import QStyleFactory, QWidget, QStyle, QColorDialog, QListWidgetItem, QMessageBox
from PyQt5.QtWidgets import qApp, QFileDialog
# Spell checker support
from manuskript import settings
from manuskript.enums import Outline
from manuskript.functions import allPaths, iconColor, writablePath, appPath, findWidgetsOfClass
from manuskript.functions import mainWindow, findBackground, themeIcon
from manuskript.ui.editors.tabSplitter import tabSplitter
from manuskript.ui.editors.themes import createThemePreview
from manuskript.ui.editors.themes import getThemeName
from manuskript.ui.editors.themes import loadThemeDatas
from manuskript.ui.settings_ui import Ui_Settings
from manuskript.ui.views.outlineView import outlineView
from manuskript.ui.views.textEditView import textEditView
from manuskript.ui.welcome import welcome
from manuskript.ui import style as S
class settingsWindow(QWidget, Ui_Settings):
def __init__(self, mainWindow):
QWidget.__init__(self)
self.setupUi(self)
self.mw = mainWindow
# UI
for l in [self.lblTitleGeneral,
self.lblTitleGeneral_2,
self.lblTitleViews,
self.lblTitleLabels,
self.lblTitleStatus,
self.lblTitleFullscreen,
]:
l.setStyleSheet(S.titleLabelSS())
icons = [QIcon.fromTheme("configure"),
QIcon.fromTheme("history-view"),
QIcon.fromTheme("gnome-settings"),
themeIcon("label"),
themeIcon("status"),
QIcon.fromTheme("preferences-desktop-theme")
]
for i in range(self.lstMenu.count()):
item = self.lstMenu.item(i)
item.setSizeHint(QSize(item.sizeHint().width(), 42))
item.setTextAlignment(Qt.AlignCenter)
if icons[i]:
item.setIcon(icons[i])
self.lstMenu.setMaximumWidth(140)
self.lstMenu.setMinimumWidth(140)
lowerKeys = [i.lower() for i in list(QStyleFactory.keys())]
# General
self.cmbStyle.addItems(list(QStyleFactory.keys()))
try:
self.cmbStyle.setCurrentIndex(lowerKeys.index(qApp.style().objectName()))
except ValueError:
self.cmbStyle.setCurrentIndex(0)
self.cmbStyle.currentIndexChanged[str].connect(self.setStyle)
self.cmbTranslation.clear()
tr = OrderedDict()
tr["English"] = ""
tr["Arabic (Saudi Arabia)"] = "manuskript_ar_SA.qm"
tr["German"] = "manuskript_de.qm"
tr["English (Great Britain)"] = "manuskript_en_GB.qm"
tr["Spanish"] = "manuskript_es.qm"
tr["Persian"] = "manuskript_fa.qm"
tr["French"] = "manuskript_fr.qm"
tr["Hungarian"] = "manuskript_hu.qm"
tr["Indonesian"] = "manuskript_id.qm"
tr["Italian"] = "manuskript_it.qm"
tr["Japanese"] = "manuskript_ja.qm"
tr["Korean"] = "manuskript_ko.qm"
tr["Norwegian Bokmål"] = "manuskript_nb_NO.qm"
tr["Dutch"] = "manuskript_nl.qm"
tr["Polish"] = "manuskript_pl.qm"
tr["Portuguese (Brazil)"] = "manuskript_pt_BR.qm"
tr["Portuguese (Portugal)"] = "manuskript_pt_PT.qm"
tr["Romanian"] = "manuskript_ro.qm"
tr["Russian"] = "manuskript_ru.qm"
tr["Svenska"] = "manuskript_sv.qm"
tr["Turkish"] = "manuskript_tr.qm"
tr["Ukrainian"] = "manuskript_uk.qm"
tr["Chinese (Simplified)"] = "manuskript_zh_CN.qm"
tr["Chinese (Traditional)"] = "manuskript_zh_HANT.qm"
self.translations = tr
for name in tr:
self.cmbTranslation.addItem(name, tr[name])
sttgs = QSettings(qApp.organizationName(), qApp.applicationName())
if (sttgs.contains("applicationTranslation")
and sttgs.value("applicationTranslation") in tr.values()):
# Sets the correct translation
self.cmbTranslation.setCurrentText(
[i for i in tr
if tr[i] == sttgs.value("applicationTranslation")][0])
self.cmbTranslation.currentIndexChanged.connect(self.setTranslation)
f = qApp.font()
self.spnGeneralFontSize.setValue(f.pointSize())
self.spnGeneralFontSize.valueChanged.connect(self.setAppFontSize)
self.chkProgressChars.setChecked(settings.progressChars);
self.chkProgressChars.stateChanged.connect(self.charSettingsChanged)
self.txtAutoSave.setValidator(QIntValidator(0, 999, self))
self.txtAutoSaveNoChanges.setValidator(QIntValidator(0, 999, self))
self.chkAutoSave.setChecked(settings.autoSave)
self.chkAutoSaveNoChanges.setChecked(settings.autoSaveNoChanges)
self.txtAutoSave.setText(str(settings.autoSaveDelay))
self.txtAutoSaveNoChanges.setText(str(settings.autoSaveNoChangesDelay))
self.chkSaveOnQuit.setChecked(settings.saveOnQuit)
self.chkSaveToZip.setChecked(settings.saveToZip)
self.chkAutoSave.stateChanged.connect(self.saveSettingsChanged)
self.chkAutoSaveNoChanges.stateChanged.connect(self.saveSettingsChanged)
self.chkSaveOnQuit.stateChanged.connect(self.saveSettingsChanged)
self.chkSaveToZip.stateChanged.connect(self.saveSettingsChanged)
self.txtAutoSave.textEdited.connect(self.saveSettingsChanged)
self.txtAutoSaveNoChanges.textEdited.connect(self.saveSettingsChanged)
autoLoad, last = self.mw.welcome.getAutoLoadValues()
self.chkAutoLoad.setChecked(autoLoad)
self.chkAutoLoad.stateChanged.connect(self.saveSettingsChanged)
# Revisions
opt = settings.revisions
self.chkRevisionsKeep.setChecked(opt["keep"])
self.chkRevisionsKeep.stateChanged.connect(self.revisionsSettingsChanged)
self.chkRevisionRemove.setChecked(opt["smartremove"])
self.chkRevisionRemove.toggled.connect(self.revisionsSettingsChanged)
self.spnRevisions10Mn.setValue(int(60 / opt["rules"][10 * 60]))
self.spnRevisions10Mn.valueChanged.connect(self.revisionsSettingsChanged)
self.spnRevisionsHour.setValue(int(60 * 10 / opt["rules"][60 * 60]))
self.spnRevisionsHour.valueChanged.connect(self.revisionsSettingsChanged)
self.spnRevisionsDay.setValue(int(60 * 60 / opt["rules"][60 * 60 * 24]))
self.spnRevisionsDay.valueChanged.connect(self.revisionsSettingsChanged)
self.spnRevisionsMonth.setValue(int(60 * 60 * 24 / opt["rules"][60 * 60 * 24 * 30]))
self.spnRevisionsMonth.valueChanged.connect(self.revisionsSettingsChanged)
self.spnRevisionsEternity.setValue(int(60 * 60 * 24 * 7 / opt["rules"][None]))
self.spnRevisionsEternity.valueChanged.connect(self.revisionsSettingsChanged)
# Views
self.tabViews.setCurrentIndex(0)
lst = ["Nothing", "POV", "Label", "Progress", "Compile"]
for cmb in self.viewSettingsDatas():
item, part = self.viewSettingsDatas()[cmb]
cmb.setCurrentIndex(lst.index(settings.viewSettings[item][part]))
cmb.currentIndexChanged.connect(self.viewSettingsChanged)
for chk in self.outlineColumnsData():
col = self.outlineColumnsData()[chk]
chk.setChecked(col in settings.outlineViewColumns)
chk.stateChanged.connect(self.outlineColumnsChanged)
self.chkOutlinePOV.setVisible(settings.viewMode != "simple") # Hides checkbox if non-fiction view mode
for item, what, value in [
(self.rdoTreeItemCount, "InfoFolder", "Count"),
(self.rdoTreeWC, "InfoFolder", "WC"),
(self.rdoTreeCC, "InfoFolder", "CC"),
(self.rdoTreeProgress, "InfoFolder", "Progress"),
(self.rdoTreeSummary, "InfoFolder", "Summary"),
(self.rdoTreeNothing, "InfoFolder", "Nothing"),
(self.rdoTreeTextWC, "InfoText", "WC"),
(self.rdoTreeTextCC, "InfoText", "CC"),
(self.rdoTreeTextProgress, "InfoText", "Progress"),
(self.rdoTreeTextSummary, "InfoText", "Summary"),
(self.rdoTreeTextNothing, "InfoText", "Nothing"),
]:
item.setChecked(settings.viewSettings["Tree"][what] == value)
item.toggled.connect(self.treeViewSettignsChanged)
self.sldTreeIconSize.valueChanged.connect(self.treeViewSettignsChanged)
self.sldTreeIconSize.valueChanged.connect(
lambda v: self.lblTreeIconSize.setText("{}x{}".format(v, v)))
self.sldTreeIconSize.setValue(settings.viewSettings["Tree"]["iconSize"])
self.chkCountSpaces.setChecked(settings.countSpaces);
self.chkCountSpaces.stateChanged.connect(self.countSpacesChanged)
self.rdoCorkOldStyle.setChecked(settings.corkStyle == "old")
self.rdoCorkNewStyle.setChecked(settings.corkStyle == "new")
self.rdoCorkNewStyle.toggled.connect(self.setCorkStyle)
self.rdoCorkOldStyle.toggled.connect(self.setCorkStyle)
self.populatesCmbBackgrounds(self.cmbCorkImage)
self.setCorkImageDefault()
self.updateCorkColor()
self.cmbCorkImage.currentIndexChanged.connect(self.setCorkBackground)
self.btnCorkColor.clicked.connect(self.setCorkColor)
# Text editor
opt = settings.textEditor
# Font
self.setButtonColor(self.btnEditorFontColor, opt["fontColor"])
self.btnEditorFontColor.clicked.connect(self.choseEditorFontColor)
self.setButtonColor(self.btnEditorMisspelledColor, opt["misspelled"])
self.btnEditorMisspelledColor.clicked.connect(self.choseEditorMisspelledColor)
self.setButtonColor(self.btnEditorBackgroundColor, opt["background"])
self.btnEditorBackgroundColor.clicked.connect(self.choseEditorBackgroundColor)
self.chkEditorBackgroundTransparent.setChecked(opt["backgroundTransparent"])
self.chkEditorBackgroundTransparent.stateChanged.connect(self.updateEditorSettings)
self.btnEditorColorDefault.clicked.connect(self.restoreEditorColors)
f = QFont()
f.fromString(opt["font"])
self.cmbEditorFontFamily.setCurrentFont(f)
self.cmbEditorFontFamily.currentFontChanged.connect(self.updateEditorSettings)
self.spnEditorFontSize.setValue(f.pointSize())
self.spnEditorFontSize.valueChanged.connect(self.updateEditorSettings)
# Cursor
self.chkEditorCursorWidth.setChecked(opt["cursorWidth"] != 1)
self.chkEditorCursorWidth.stateChanged.connect(self.updateEditorSettings)
self.spnEditorCursorWidth.setValue(opt["cursorWidth"] if opt["cursorWidth"] != 1 else 9)
self.spnEditorCursorWidth.valueChanged.connect(self.updateEditorSettings)
self.spnEditorCursorWidth.setEnabled(opt["cursorWidth"] != 1)
self.chkEditorNoBlinking.setChecked(opt["cursorNotBlinking"])
self.chkEditorNoBlinking.stateChanged.connect(self.setApplicationCursorBlinking)
self.chkEditorTypeWriterMode.setChecked(opt["alwaysCenter"])
self.chkEditorTypeWriterMode.stateChanged.connect(self.updateEditorSettings)
self.cmbEditorFocusMode.setCurrentIndex(
0 if not opt["focusMode"] else
1 if opt["focusMode"] == "sentence" else
2 if opt["focusMode"] == "line" else
3)
self.cmbEditorFocusMode.currentIndexChanged.connect(self.updateEditorSettings)
# Text areas
self.chkEditorMaxWidth.setChecked(opt["maxWidth"] != 0)
self.chkEditorMaxWidth.stateChanged.connect(self.updateEditorSettings)
self.spnEditorMaxWidth.setEnabled(opt["maxWidth"] != 0)
self.spnEditorMaxWidth.setValue(500 if opt["maxWidth"] == 0 else opt["maxWidth"])
self.spnEditorMaxWidth.valueChanged.connect(self.updateEditorSettings)
self.spnEditorMarginsLR.setValue(opt["marginsLR"])
self.spnEditorMarginsLR.valueChanged.connect(self.updateEditorSettings)
self.spnEditorMarginsTB.setValue(opt["marginsTB"])
self.spnEditorMarginsTB.valueChanged.connect(self.updateEditorSettings)
# Paragraphs
self.cmbEditorAlignment.setCurrentIndex(opt["textAlignment"])
self.cmbEditorAlignment.currentIndexChanged.connect(self.updateEditorSettings)
self.cmbEditorLineSpacing.setCurrentIndex(
0 if opt["lineSpacing"] == 100 else
1 if opt["lineSpacing"] == 150 else
2 if opt["lineSpacing"] == 200 else
3)
self.cmbEditorLineSpacing.currentIndexChanged.connect(self.updateEditorSettings)
self.spnEditorLineSpacing.setValue(opt["lineSpacing"])
self.spnEditorLineSpacing.valueChanged.connect(self.updateEditorSettings)
self.spnEditorLineSpacing.setEnabled(opt["lineSpacing"] not in [100, 150, 200])
self.spnEditorLineSpacing.valueChanged.connect(self.updateEditorSettings)
self.spnEditorTabWidth.setValue(opt["tabWidth"])
self.spnEditorTabWidth.valueChanged.connect(self.updateEditorSettings)
self.chkEditorIndent.setChecked(opt["indent"])
self.chkEditorIndent.stateChanged.connect(self.updateEditorSettings)
self.spnEditorParaAbove.setValue(opt["spacingAbove"])
self.spnEditorParaAbove.valueChanged.connect(self.updateEditorSettings)
self.spnEditorParaBelow.setValue(opt["spacingBelow"])
self.spnEditorParaBelow.valueChanged.connect(self.updateEditorSettings)
self.timerUpdateWidgets = QTimer()
self.timerUpdateWidgets.setSingleShot(True)
self.timerUpdateWidgets.setInterval(250)
self.timerUpdateWidgets.timeout.connect(self.updateAllWidgets)
# Labels
self.lstLabels.setModel(self.mw.mdlLabels)
self.lstLabels.setRowHidden(0, True)
self.lstLabels.clicked.connect(self.updateLabelColor)
self.btnLabelAdd.clicked.connect(self.addLabel)
self.btnLabelRemove.clicked.connect(self.removeLabel)
self.btnLabelColor.clicked.connect(self.setLabelColor)
# Statuses
self.lstStatus.setModel(self.mw.mdlStatus)
self.lstStatus.setRowHidden(0, True)
self.btnStatusAdd.clicked.connect(self.addStatus)
self.btnStatusRemove.clicked.connect(self.removeStatus)
# Fullscreen
self._editingTheme = None
self.btnThemeEditOK.setIcon(qApp.style().standardIcon(QStyle.SP_DialogApplyButton))
self.btnThemeEditOK.clicked.connect(self.saveTheme)
self.btnThemeEditCancel.setIcon(qApp.style().standardIcon(QStyle.SP_DialogCancelButton))
self.btnThemeEditCancel.clicked.connect(self.cancelEdit)
self.cmbThemeEdit.currentIndexChanged.connect(self.themeEditStack.setCurrentIndex)
self.cmbThemeEdit.setCurrentIndex(0)
self.cmbThemeEdit.currentIndexChanged.emit(0)
self.themeStack.setCurrentIndex(0)
self.lstThemes.currentItemChanged.connect(self.themeSelected)
self.populatesThemesList()
self.btnThemeAdd.clicked.connect(self.newTheme)
self.btnThemeEdit.clicked.connect(self.editTheme)
self.btnThemeRemove.clicked.connect(self.removeTheme)
self.timerUpdateFSPreview = QTimer()
self.timerUpdateFSPreview.setSingleShot(True)
self.timerUpdateFSPreview.setInterval(250)
self.timerUpdateFSPreview.timeout.connect(self.updatePreview)
def setTab(self, tab):
tabs = {
"General": 0,
"Views": 1,
"Labels": 2,
"Status": 3,
"Fullscreen": 4,
}
if tab in tabs:
self.lstMenu.setCurrentRow(tabs[tab])
else:
self.lstMenu.setCurrentRow(tab)
####################################################################################################
# GENERAL #
####################################################################################################
def setStyle(self, style):
# Save style to Qt Settings
sttgs = QSettings(qApp.organizationName(), qApp.applicationName())
sttgs.setValue("applicationStyle", style)
qApp.setStyle(style)
def setTranslation(self, index):
path = self.cmbTranslation.currentData()
# Save settings
sttgs = QSettings(qApp.organizationName(), qApp.applicationName())
sttgs.setValue("applicationTranslation", path)
# QMessageBox.information(self, "Warning", "You'll have to restart manuskript.")
def setAppFontSize(self, val):
"""
Set application default font point size.
"""
f = qApp.font()
f.setPointSize(val)
qApp.setFont(f)
mainWindow().setFont(f)
sttgs = QSettings(qApp.organizationName(), qApp.applicationName())
sttgs.setValue("appFontSize", val)
def charSettingsChanged(self):
settings.progressChars = True if self.chkProgressChars.checkState() else False
self.mw.mainEditor.updateStats()
def saveSettingsChanged(self):
if self.txtAutoSave.text() in ["", "0"]:
self.txtAutoSave.setText("1")
if self.txtAutoSaveNoChanges.text() in ["", "0"]:
self.txtAutoSaveNoChanges.setText("1")
sttgs = QSettings()
sttgs.setValue("autoLoad", True if self.chkAutoLoad.checkState() else False)
sttgs.sync()
settings.autoSave = True if self.chkAutoSave.checkState() else False
settings.autoSaveNoChanges = True if self.chkAutoSaveNoChanges.checkState() else False
settings.saveOnQuit = True if self.chkSaveOnQuit.checkState() else False
settings.saveToZip = True if self.chkSaveToZip.checkState() else False
settings.autoSaveDelay = int(self.txtAutoSave.text())
settings.autoSaveNoChangesDelay = int(self.txtAutoSaveNoChanges.text())
self.mw.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000)
self.mw.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay * 1000)
####################################################################################################
# REVISION #
####################################################################################################
def revisionsSettingsChanged(self):
opt = settings.revisions
opt["keep"] = True if self.chkRevisionsKeep.checkState() else False
opt["smartremove"] = self.chkRevisionRemove.isChecked()
opt["rules"][10 * 60] = 60 / self.spnRevisions10Mn.value()
opt["rules"][60 * 60] = 60 * 10 / self.spnRevisionsHour.value()
opt["rules"][60 * 60 * 24] = 60 * 60 / self.spnRevisionsDay.value()
opt["rules"][60 * 60 * 24 * 30] = 60 * 60 * 24 / self.spnRevisionsMonth.value()
opt["rules"][None] = 60 * 60 * 24 * 7 / self.spnRevisionsEternity.value()
####################################################################################################
# VIEWS #
####################################################################################################
def viewSettingsDatas(self):
return {
self.cmbTreeIcon: ("Tree", "Icon"),
self.cmbTreeText: ("Tree", "Text"),
self.cmbTreeBackground: ("Tree", "Background"),
self.cmbOutlineIcon: ("Outline", "Icon"),
self.cmbOutlineText: ("Outline", "Text"),
self.cmbOutlineBackground: ("Outline", "Background"),
self.cmbCorkIcon: ("Cork", "Icon"),
self.cmbCorkText: ("Cork", "Text"),
self.cmbCorkBackground: ("Cork", "Background"),
self.cmbCorkBorder: ("Cork", "Border"),
self.cmbCorkCorner: ("Cork", "Corner")
}
def viewSettingsChanged(self):
cmb = self.sender()
lst = ["Nothing", "POV", "Label", "Progress", "Compile"]
item, part = self.viewSettingsDatas()[cmb]
element = lst[cmb.currentIndex()]
self.mw.setViewSettings(item, part, element)
self.mw.generateViewMenu()
def outlineColumnsData(self):
return {
self.chkOutlineTitle: Outline.title,
self.chkOutlinePOV: Outline.POV,
self.chkOutlineLabel: Outline.label,
self.chkOutlineStatus: Outline.status,
self.chkOutlineCompile: Outline.compile,
self.chkOutlineWordCount: Outline.wordCount,
self.chkOutlineGoal: Outline.goal,
self.chkOutlinePercentage: Outline.goalPercentage,
}
def outlineColumnsChanged(self):
chk = self.sender()
val = True if chk.checkState() else False
col = self.outlineColumnsData()[chk]
if val and not col in settings.outlineViewColumns:
settings.outlineViewColumns.append(col)
elif not val and col in settings.outlineViewColumns:
settings.outlineViewColumns.remove(col)
# Update views
for w in findWidgetsOfClass(outlineView):
w.hideColumns()
def treeViewSettignsChanged(self):
for item, what, value in [
(self.rdoTreeItemCount, "InfoFolder", "Count"),
(self.rdoTreeWC, "InfoFolder", "WC"),
(self.rdoTreeCC, "InfoFolder", "CC"),
(self.rdoTreeProgress, "InfoFolder", "Progress"),
(self.rdoTreeSummary, "InfoFolder", "Summary"),
(self.rdoTreeNothing, "InfoFolder", "Nothing"),
(self.rdoTreeTextWC, "InfoText", "WC"),
(self.rdoTreeTextCC, "InfoText", "CC"),
(self.rdoTreeTextProgress, "InfoText", "Progress"),
(self.rdoTreeTextSummary, "InfoText", "Summary"),
(self.rdoTreeTextNothing, "InfoText", "Nothing"),
]:
if item.isChecked():
settings.viewSettings["Tree"][what] = value
iconSize = self.sldTreeIconSize.value()
if iconSize != settings.viewSettings["Tree"]["iconSize"]:
settings.viewSettings["Tree"]["iconSize"] = iconSize
self.mw.treeRedacOutline.setIconSize(QSize(iconSize, iconSize))
self.mw.treeRedacOutline.viewport().update()
def countSpacesChanged(self):
settings.countSpaces = True if self.chkCountSpaces.checkState() else False
self.mw.mainEditor.updateStats()
def setCorkColor(self):
color = QColor(settings.corkBackground["color"])
self.colorDialog = QColorDialog(color, self)
color = self.colorDialog.getColor(color)
if color.isValid():
settings.corkBackground["color"] = color.name()
self.updateCorkColor()
# Update Cork view
self.mw.mainEditor.updateCorkBackground()
def setCorkStyle(self):
settings.corkStyle = "new" if self.rdoCorkNewStyle.isChecked() else "old"
self.mw.mainEditor.updateCorkView()
def updateCorkColor(self):
self.btnCorkColor.setStyleSheet("background:{};".format(settings.corkBackground["color"]))
def setCorkBackground(self, i):
# Check if combobox was reset
if i == -1:
return
img = self.cmbCorkImage.itemData(i)
img = os.path.basename(img)
if img:
settings.corkBackground["image"] = img
else:
txt = self.cmbCorkImage.itemText(i)
if txt == "":
settings.corkBackground["image"] = ""
else:
img = self.addBackgroundImage()
if img:
self.populatesCmbBackgrounds(self.cmbCorkImage)
settings.corkBackground["image"] = img
self.setCorkImageDefault()
# Update Cork view
self.mw.mainEditor.updateCorkBackground()
def populatesCmbBackgrounds(self, cmb):
# self.cmbDelegate = cmbPixmapDelegate()
# self.cmbCorkImage.setItemDelegate(self.cmbDelegate)
paths = allPaths(os.path.join("resources", "backgrounds"))
cmb.clear()
cmb.addItem(QIcon.fromTheme("list-remove"), "", "")
for p in paths:
lst = os.listdir(p)
for l in lst:
if l.lower()[-4:] in [".jpg", ".png"] or \
l.lower()[-5:] in [".jpeg"]:
px = QPixmap(os.path.join(p, l)).scaled(128, 64, Qt.KeepAspectRatio)
cmb.addItem(QIcon(px), "", os.path.join(p, l))
cmb.addItem(QIcon.fromTheme("list-add"), " ", "")
cmb.setIconSize(QSize(128, 64))
def addBackgroundImage(self):
lastDirectory = self.mw.welcome.getLastAccessedDirectory()
"""File dialog that request an existing file. For opening an image."""
filename = QFileDialog.getOpenFileName(self,
self.tr("Open Image"),
lastDirectory,
self.tr("Image files (*.jpg; *.jpeg; *.png)"))[0]
if filename:
try:
px = QPixmap()
valid = px.load(filename)
del px
if valid:
shutil.copy(filename, writablePath(os.path.join("resources", "backgrounds")))
return os.path.basename(filename)
else:
QMessageBox.warning(self, self.tr("Error"),
self.tr("Unable to load selected file"))
except Exception as e:
QMessageBox.warning(self, self.tr("Error"),
self.tr("Unable to add selected image:\n{}").format(str(e)))
return None
def setCorkImageDefault(self):
if settings.corkBackground["image"] != "":
i = self.cmbCorkImage.findData(findBackground(settings.corkBackground["image"]))
if i != -1:
self.cmbCorkImage.setCurrentIndex(i)
####################################################################################################
# VIEWS / EDITOR
####################################################################################################
def updateEditorSettings(self):
"""
Stores settings for editor appearance.
"""
# Background
settings.textEditor["backgroundTransparent"] = True if self.chkEditorBackgroundTransparent.checkState() else False
# Font
f = self.cmbEditorFontFamily.currentFont()
f.setPointSize(self.spnEditorFontSize.value())
settings.textEditor["font"] = f.toString()
# Cursor
settings.textEditor["cursorWidth"] = \
1 if not self.chkEditorCursorWidth.isChecked() else \
self.spnEditorCursorWidth.value()
self.spnEditorCursorWidth.setEnabled(self.chkEditorCursorWidth.isChecked())
settings.textEditor["alwaysCenter"] = self.chkEditorTypeWriterMode.isChecked()
settings.textEditor["focusMode"] = \
False if self.cmbEditorFocusMode.currentIndex() == 0 else \
"sentence" if self.cmbEditorFocusMode.currentIndex() == 1 else \
"line" if self.cmbEditorFocusMode.currentIndex() == 2 else \
"paragraph"
# Text area
settings.textEditor["maxWidth"] = \
0 if not self.chkEditorMaxWidth.isChecked() else \
self.spnEditorMaxWidth.value()
self.spnEditorMaxWidth.setEnabled(self.chkEditorMaxWidth.isChecked())
settings.textEditor["marginsLR"] = self.spnEditorMarginsLR.value()
settings.textEditor["marginsTB"] = self.spnEditorMarginsTB.value()
# Paragraphs
settings.textEditor["textAlignment"] = self.cmbEditorAlignment.currentIndex()
settings.textEditor["lineSpacing"] = \
100 if self.cmbEditorLineSpacing.currentIndex() == 0 else \
150 if self.cmbEditorLineSpacing.currentIndex() == 1 else \
200 if self.cmbEditorLineSpacing.currentIndex() == 2 else \
self.spnEditorLineSpacing.value()
self.spnEditorLineSpacing.setEnabled(self.cmbEditorLineSpacing.currentIndex() == 3)
settings.textEditor["tabWidth"] = self.spnEditorTabWidth.value()
settings.textEditor["indent"] = True if self.chkEditorIndent.checkState() else False
settings.textEditor["spacingAbove"] = self.spnEditorParaAbove.value()
settings.textEditor["spacingBelow"] = self.spnEditorParaBelow.value()
self.timerUpdateWidgets.start()
def updateAllWidgets(self):
# Update font and defaultBlockFormat to all textEditView. Drastically.
for w in mainWindow().findChildren(textEditView, QRegExp(".*")):
w.loadFontSettings()
# Update background color in all tabSplitter (tabs)
for w in mainWindow().findChildren(tabSplitter, QRegExp(".*")):
w.updateStyleSheet()
# Update background color in all folder text view:
for w in mainWindow().findChildren(QWidget, QRegExp("editorWidgetFolderText")):
w.setStyleSheet("background: {};".format(settings.textEditor["background"]))
def setApplicationCursorBlinking(self):
settings.textEditor["cursorNotBlinking"] = self.chkEditorNoBlinking.isChecked()
if settings.textEditor["cursorNotBlinking"]:
qApp.setCursorFlashTime(0)
else:
# Load default system value, that we cached at startup
qApp.setCursorFlashTime(self.mw._defaultCursorFlashTime)
def choseEditorFontColor(self):
color = settings.textEditor["fontColor"]
self.colorDialog = QColorDialog(QColor(color), self)
color = self.colorDialog.getColor(QColor(color))
if color.isValid():
settings.textEditor["fontColor"] = color.name()
self.setButtonColor(self.btnEditorFontColor, color.name())
self.updateEditorSettings()
def choseEditorMisspelledColor(self):
color = settings.textEditor["misspelled"]
self.colorDialog = QColorDialog(QColor(color), self)
color = self.colorDialog.getColor(QColor(color))
if color.isValid():
settings.textEditor["misspelled"] = color.name()
self.setButtonColor(self.btnEditorMisspelledColor, color.name())
self.updateEditorSettings()
def choseEditorBackgroundColor(self):
color = settings.textEditor["background"]
self.colorDialog = QColorDialog(QColor(color), self)
color = self.colorDialog.getColor(QColor(color))
if color.isValid():
settings.textEditor["background"] = color.name()
self.setButtonColor(self.btnEditorBackgroundColor, color.name())
self.updateEditorSettings()
def restoreEditorColors(self):
settings.textEditor["background"] = S.base
self.setButtonColor(self.btnEditorBackgroundColor, S.base)
settings.textEditor["fontColor"] = S.text
self.setButtonColor(self.btnEditorFontColor, S.text)
self.updateEditorSettings()
####################################################################################################
# STATUS #
####################################################################################################
def addStatus(self):
self.mw.mdlStatus.appendRow(QStandardItem(self.tr("New status")))
def removeStatus(self):
for i in self.lstStatus.selectedIndexes():
self.mw.mdlStatus.removeRows(i.row(), 1)
####################################################################################################
# LABELS #
####################################################################################################
def updateLabelColor(self, index):
# px = QPixmap(64, 64)
# px.fill(iconColor(self.mw.mdlLabels.item(index.row()).icon()))
# self.btnLabelColor.setIcon(QIcon(px))
self.btnLabelColor.setStyleSheet("background:{};".format(
iconColor(self.mw.mdlLabels.item(index.row()).icon()).name()))
self.btnLabelColor.setEnabled(True)
def addLabel(self):
px = QPixmap(32, 32)
px.fill(Qt.transparent)
self.mw.mdlLabels.appendRow(QStandardItem(QIcon(px), self.tr("New label")))
def removeLabel(self):
for i in self.lstLabels.selectedIndexes():
self.mw.mdlLabels.removeRows(i.row(), 1)
def setLabelColor(self):
index = self.lstLabels.currentIndex()
color = iconColor(self.mw.mdlLabels.item(index.row()).icon())
self.colorDialog = QColorDialog(color, self)
color = self.colorDialog.getColor(color)
if color.isValid():
px = QPixmap(32, 32)
px.fill(color)
self.mw.mdlLabels.item(index.row()).setIcon(QIcon(px))
self.updateLabelColor(index)
####################################################################################################
# FULLSCREEN #
####################################################################################################
def themeSelected(self, current, previous):
if current:
# UI updates
self.btnThemeEdit.setEnabled(current.data(Qt.UserRole + 1))
self.btnThemeRemove.setEnabled(current.data(Qt.UserRole + 1))
# Save settings
theme = current.data(Qt.UserRole)
settings.fullScreenTheme = os.path.splitext(os.path.split(theme)[1])[0]
else:
# UI updates
self.btnThemeEdit.setEnabled(False)
self.btnThemeRemove.setEnabled(False)
def newTheme(self):
path = writablePath(os.path.join("resources", "themes"))
name = self.tr("newtheme")
if os.path.exists(os.path.join(path, "{}.theme".format(name))):
i = 1
while os.path.exists(os.path.join(path, "{}_{}.theme".format(name, i))):
i += 1
name = os.path.join(path, "{}_{}.theme".format(name, i))
else:
name = os.path.join(path, "{}.theme".format(name))
settings = QSettings(name, QSettings.IniFormat)
settings.setValue("Name", self.tr("New theme"))
settings.sync()
self.populatesThemesList()
def editTheme(self):
item = self.lstThemes.currentItem()
theme = item.data(Qt.UserRole)
self.loadTheme(theme)
self.themeStack.setCurrentIndex(1)
def removeTheme(self):
item = self.lstThemes.currentItem()
theme = item.data(Qt.UserRole)
os.remove(theme)
self.populatesThemesList()
def populatesThemesList(self):
paths = allPaths(os.path.join("resources", "themes"))
current = settings.fullScreenTheme
self.lstThemes.clear()
for p in paths:
lst = [i for i in os.listdir(p) if os.path.splitext(i)[1] == ".theme"]
for t in lst:
theme = os.path.join(p, t)
editable = not appPath() in theme
n = getThemeName(theme)
item = QListWidgetItem(n)
item.setData(Qt.UserRole, theme)
item.setData(Qt.UserRole + 1, editable)
item.setToolTip("{}{}".format(
n,
self.tr(" (read-only)") if not editable else ""))
thumb = os.path.join(p, t.replace(".theme", ".jpg"))
px = QPixmap(200, 120)
px.fill(Qt.white)
if not os.path.exists(thumb):
currentScreen = qApp.desktop().screenNumber(self)
screenRect = qApp.desktop().screenGeometry(currentScreen)
thumb = createThemePreview(theme, screenRect)
icon = QPixmap(thumb).scaled(200, 120, Qt.KeepAspectRatio)
painter = QPainter(px)
painter.drawPixmap(px.rect().center() - icon.rect().center(), icon)
painter.end()
item.setIcon(QIcon(px))
self.lstThemes.addItem(item)
if current and current in t:
self.lstThemes.setCurrentItem(item)
current = None
self.lstThemes.setIconSize(QSize(200, 120))
if current: # the theme from settings wasn't found
# select the last from the list
self.lstThemes.setCurrentRow(self.lstThemes.count() - 1)
def loadTheme(self, theme):
self._editingTheme = theme
self._loadingTheme = True # So we don't generate preview while loading
# Load datas
self._themeData = loadThemeDatas(theme)
# Window Background
self.btnThemWindowBackgroundColor.clicked.connect(lambda: self.getThemeColor("Background/Color"))
try:
self.cmbThemeBackgroundImage.disconnect()
except:
pass
self.populatesCmbBackgrounds(self.cmbThemeBackgroundImage)
self.cmbThemeBackgroundImage.currentIndexChanged.connect(self.updateThemeBackground)
self.cmbThemBackgroundType.currentIndexChanged.connect(lambda i: self.setSetting("Background/Type", i))
# Text Background
self.btnThemeTextBackgroundColor.clicked.connect(lambda: self.getThemeColor("Foreground/Color"))
self.spnThemeTextBackgroundOpacity.valueChanged.connect(lambda v: self.setSetting("Foreground/Opacity", v))
self.spnThemeTextMargins.valueChanged.connect(lambda v: self.setSetting("Foreground/Margin", v))
self.spnThemeTextPadding.valueChanged.connect(lambda v: self.setSetting("Foreground/Padding", v))
self.cmbThemeTextPosition.currentIndexChanged.connect(lambda i: self.setSetting("Foreground/Position", i))
self.spnThemeTextRadius.valueChanged.connect(lambda v: self.setSetting("Foreground/Rounding", v))
self.spnThemeTextWidth.valueChanged.connect(lambda v: self.setSetting("Foreground/Width", v))
# Text Options
self.btnThemeTextColor.clicked.connect(lambda: self.getThemeColor("Text/Color"))
self.cmbThemeFont.currentFontChanged.connect(self.updateThemeFont)
try:
self.cmbThemeFontSize.currentIndexChanged.disconnect(self.updateThemeFont)
except:
pass
self.populatesFontSize()
self.cmbThemeFontSize.currentIndexChanged.connect(self.updateThemeFont)
self.btnThemeMisspelledColor.clicked.connect(lambda: self.getThemeColor("Text/Misspelled"))
# Paragraph Options
self.chkThemeIndent.stateChanged.connect(lambda v: self.setSetting("Spacings/IndentFirstLine", v != 0))
self.cmbThemeAlignment.currentIndexChanged.connect(lambda i: self.setSetting("Spacings/Alignment", i))
self.cmbThemeLineSpacing.currentIndexChanged.connect(self.updateLineSpacing)
self.cmbThemeLineSpacing.currentIndexChanged.connect(self.updateLineSpacing)
self.spnThemeLineSpacing.valueChanged.connect(lambda v: self.setSetting("Spacings/LineSpacing", v))
self.spnThemeParaAbove.valueChanged.connect(lambda v: self.setSetting("Spacings/ParagraphAbove", v))
self.spnThemeParaBelow.valueChanged.connect(lambda v: self.setSetting("Spacings/ParagraphBelow", v))
self.spnThemeTabWidth.valueChanged.connect(lambda v: self.setSetting("Spacings/TabWidth", v))
# Update UI
self.updateUIFromTheme()
# Generate preview
self._loadingTheme = False
self.updatePreview()
def setSetting(self, key, val):
self._themeData[key] = val
self.timerUpdateFSPreview.start()
def updateUIFromTheme(self):
self.txtThemeName.setText(self._themeData["Name"])
# Window Background
self.setButtonColor(self.btnThemWindowBackgroundColor, self._themeData["Background/Color"])
i = self.cmbThemeBackgroundImage.findData(self._themeData["Background/ImageFile"], flags=Qt.MatchContains)
if i != -1:
self.cmbThemeBackgroundImage.setCurrentIndex(i)
self.cmbThemBackgroundType.setCurrentIndex(self._themeData["Background/Type"])
# Text background
self.setButtonColor(self.btnThemeTextBackgroundColor, self._themeData["Foreground/Color"])
self.spnThemeTextBackgroundOpacity.setValue(self._themeData["Foreground/Opacity"])
self.spnThemeTextMargins.setValue(self._themeData["Foreground/Margin"])
self.spnThemeTextPadding.setValue(self._themeData["Foreground/Padding"])
self.cmbThemeTextPosition.setCurrentIndex(self._themeData["Foreground/Position"])
self.spnThemeTextRadius.setValue(self._themeData["Foreground/Rounding"])
self.spnThemeTextWidth.setValue(self._themeData["Foreground/Width"])
# Text Options
self.setButtonColor(self.btnThemeTextColor, self._themeData["Text/Color"])
f = QFont()
f.fromString(self._themeData["Text/Font"])
self.cmbThemeFont.setCurrentFont(f)
i = self.cmbThemeFontSize.findText(str(f.pointSize()))
if i != -1:
self.cmbThemeFontSize.setCurrentIndex(i)
else:
self.cmbThemeFontSize.addItem(str(f.pointSize()))
self.cmbThemeFontSize.setCurrentIndex(self.cmbThemeFontSize.count() - 1)
self.setButtonColor(self.btnThemeMisspelledColor, self._themeData["Text/Misspelled"])
# Paragraph Options
self.chkThemeIndent.setCheckState(Qt.Checked if self._themeData["Spacings/IndentFirstLine"] else Qt.Unchecked)
self.spnThemeLineSpacing.setEnabled(False)
self.cmbThemeAlignment.setCurrentIndex(self._themeData["Spacings/Alignment"])
if self._themeData["Spacings/LineSpacing"] == 100:
self.cmbThemeLineSpacing.setCurrentIndex(0)
elif self._themeData["Spacings/LineSpacing"] == 150:
self.cmbThemeLineSpacing.setCurrentIndex(1)
elif self._themeData["Spacings/LineSpacing"] == 200:
self.cmbThemeLineSpacing.setCurrentIndex(2)
else:
self.cmbThemeLineSpacing.setCurrentIndex(3)
self.spnThemeLineSpacing.setEnabled(True)
self.spnThemeLineSpacing.setValue(self._themeData["Spacings/LineSpacing"])
self.spnThemeParaAbove.setValue(self._themeData["Spacings/ParagraphAbove"])
self.spnThemeParaBelow.setValue(self._themeData["Spacings/ParagraphBelow"])
self.spnThemeTabWidth.setValue(self._themeData["Spacings/TabWidth"])
def populatesFontSize(self):
self.cmbThemeFontSize.clear()
s = list(range(6, 13)) + list(range(14, 29, 2)) + [36, 48, 72]
for i in s:
self.cmbThemeFontSize.addItem(str(i))
def updateThemeFont(self, v):
f = self.cmbThemeFont.currentFont()
s = self.cmbThemeFontSize.itemText(self.cmbThemeFontSize.currentIndex())
if s:
f.setPointSize(int(s))
self._themeData["Text/Font"] = f.toString()
self.timerUpdateFSPreview.start()
def updateLineSpacing(self, i):
if i == 0:
self._themeData["Spacings/LineSpacing"] = 100
elif i == 1:
self._themeData["Spacings/LineSpacing"] = 150
elif i == 2:
self._themeData["Spacings/LineSpacing"] = 200
elif i == 3:
self._themeData["Spacings/LineSpacing"] = self.spnThemeLineSpacing.value()
self.spnThemeLineSpacing.setEnabled(i == 3)
self.timerUpdateFSPreview.start()
def updateThemeBackground(self, i):
# Check if combobox was reset
if i == -1:
return
img = self.cmbThemeBackgroundImage.itemData(i)
if img:
self._themeData["Background/ImageFile"] = os.path.split(img)[1]
else:
txt = self.cmbThemeBackgroundImage.itemText(i)
if txt == "":
self._themeData["Background/ImageFile"] = ""
else:
img = self.addBackgroundImage()
if img:
self.populatesCmbBackgrounds(self.cmbThemeBackgroundImage)
self._themeData["Background/ImageFile"] = img
i = self.cmbThemeBackgroundImage.findData(self._themeData["Background/ImageFile"], flags=Qt.MatchContains)
if i != -1:
self.cmbThemeBackgroundImage.setCurrentIndex(i)
self.updatePreview()
def getThemeColor(self, key):
color = self._themeData[key]
self.colorDialog = QColorDialog(QColor(color), self)
color = self.colorDialog.getColor(QColor(color))
if color.isValid():
self._themeData[key] = color.name()
self.updateUIFromTheme()
self.updatePreview()
def updatePreview(self):
if self._loadingTheme:
return
currentScreen = qApp.desktop().screenNumber(self)
screen = qApp.desktop().screenGeometry(currentScreen)
px = createThemePreview(self._themeData, screen, self.lblPreview.size())
self.lblPreview.setPixmap(px)
def setButtonColor(self, btn, color):
btn.setStyleSheet("background:{};".format(color))
def saveTheme(self):
settings = QSettings(self._editingTheme, QSettings.IniFormat)
self._themeData["Name"] = self.txtThemeName.text()
for key in self._themeData:
settings.setValue(key, self._themeData[key])
settings.sync()
self.populatesThemesList()
self.themeStack.setCurrentIndex(0)
self._editingTheme = None
def cancelEdit(self):
self.themeStack.setCurrentIndex(0)
self._editingTheme = None
def resizeEvent(self, event):
QWidget.resizeEvent(self, event)
if self._editingTheme:
self.updatePreview()