<commit_before>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 23 21:30:34 2017

@author: Aeolitus
"""
from PyQt5 import QtWidgets, QtCore, QtGui
import sys
import logging
import os
import os.path
import UI.MainWindow
import CharakterEditor
import DatenbankEdit
import UI.CharakterMain
import UI.DatenbankMain
from Wolke import Wolke
import yaml
from EinstellungenWrapper import EinstellungenWrapper
from HilfeWrapper import HilfeWrapper
import Version
from EventBus import EventBus
from PluginLoader import PluginLoader
from UpdateChecker import UpdateChecker
from PyQt5.QtGui import QPalette, QColor
from PyQt5.QtWidgets import QToolTip

loglevels = {0: logging.ERROR, 1: logging.WARNING, 2: logging.DEBUG}
logging.basicConfig(filename="sephrasto.log", \
    level=loglevels[Wolke.Settings['Logging']], \
    format="%(asctime)s | %(levelname)s | %(filename)s::%(funcName)s(%(lineno)d) | %(message)s")

def sephrasto_excepthook(exc_type, exc_value, tb):
    traceback = [' Traceback (most recent call last):']
    while tb:
        filename = tb.tb_frame.f_code.co_filename
        name = tb.tb_frame.f_code.co_name
        lineno = tb.tb_lineno
        traceback.append('   File "%.500s", line %d, in %.500s' %(filename, lineno, name))
        tb = tb.tb_next

    # Exception type and value
    exception = ' %s: %s' %(exc_type.__name__, exc_value)
    logging.critical(exception + "\n".join(traceback))

    #Try to show message box, hopefully its not a crash in Qt
    messagebox = QtWidgets.QMessageBox()
    messagebox.setWindowTitle("Fehler!")
    messagebox.setText("Unerwarteter Fehler:" + exception + ". Bei Fragen zum diesem Fehler bitte sephrasto.log mitsenden.")
    messagebox.setIcon(QtWidgets.QMessageBox.Critical)
    messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
    messagebox.exec_()

class PluginData(object):
    def __init__(self, name, description, isOfficial, plugin):
        super().__init__()
        self.name = name
        self.description = description
        self.isOfficial = isOfficial
        self.plugin = plugin

class MainWindowWrapper(object):
    '''
    Main Class responsible for running the entire application.
    Just shows three buttons and handles the execution of the individual subparts.
    '''
    def __init__(self):
        sys.excepthook = sephrasto_excepthook

        '''
        Initializes the GUI and connects the buttons.
        '''
        self._version_ = "v" + str(Version._sephrasto_version_major) + "." + str(Version._sephrasto_version_minor) + "." + str(Version._sephrasto_version_build)
        logging.critical("Starte Sephrasto " + self._version_) #critical so it's always printed, independent of the debug level setting

        super().__init__()

        os.chdir(os.path.dirname(os.path.abspath(__file__)))

        #Make sure the application scales properly, i.e. in Win10 users can change the UI scale in the display settings
        if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'):
            QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
        if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
            QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)

        self.app = QtCore.QCoreApplication.instance()
        if self.app is None:
            self.app = QtWidgets.QApplication(sys.argv)

        self.Form = QtWidgets.QWidget()
        self.ui = UI.MainWindow.Ui_Form()
        self.ui.setupUi(self.Form)
        self.ui.buttonNew.clicked.connect(self.createNew)
        self.ui.buttonEdit.clicked.connect(self.editExisting)
        self.ui.buttonRules.clicked.connect(self.editRuleset)
        self.ui.buttonSettings.clicked.connect(self.editSettings)
        self.ui.buttonHelp.clicked.connect(self.help)
        self.ui.labelVersion.setText(self._version_ + " - by Aeolitus ")

        self.app.setWindowIcon(QtGui.QIcon('icon_large.png'))

        # Get the Settings loaded
        EinstellungenWrapper.load()
        logging.getLogger().setLevel(loglevels[Wolke.Settings['Logging']])

        self.updateTheme()

        UpdateChecker.checkForUpdate()

        self._plugins = []
        pluginPaths = ["Plugins", Wolke.Settings['Pfad-Plugins']]

        for pluginPath in pluginPaths:
            pluginNames = PluginLoader.getPlugins(pluginPath)

            for pluginName in pluginNames:
                description = PluginLoader.getPluginDescription(pluginPath, pluginName)
                if pluginName in Wolke.Settings['Deaktivierte-Plugins']:
                    self._plugins.append(PluginData(pluginName, description, pluginPath == "Plugins", None))
                    continue

                plugin = PluginLoader.loadPlugin(pluginPath, pluginName)
                self._plugins.append(PluginData(pluginName, description, pluginPath == "Plugins", plugin))
                logging.critical("Plugin: loaded " + pluginName)
                if hasattr(plugin, "createMainWindowButtons"):
                    for button in plugin.createMainWindowButtons():
                        button.setParent(self.Form)
                        button.setMinimumSize(QtCore.QSize(0, 25))
                        self.ui.vlPluginButtons.addWidget(button)

        EventBus.doAction("plugins_geladen")

        EventBus.addAction("charaktereditor_reload", self.charakterEditorReloadHook)
        EventBus.addAction("charaktereditor_modified", self.charakterEditorModifiedHook)

        self.Form.show()
        sys.exit(self.app.exec_())

    def charakterEditorReloadHook(self, params):
        if self.ed and not self.ed.formMain.isHidden():
            self.ed.reloadAll()

    def charakterEditorModifiedHook(self, params):
        if hasattr(self, "ed") and self.ed and not self.ed.formMain.isHidden():
            self.ed.onModified()

    def createNew(self):
        '''
        Creates a new CharakterEditor which is empty and shows it.
        '''
        self.ed = CharakterEditor.Editor(self._plugins, self.savePathUpdated)
        if self.ed.noDatabase:
            raise Exception("Konnte datenbank.xml nicht finden")
        EventBus.doAction("charaktereditor_oeffnet", { "neu" : True, "filepath" : "" })
        self.ed.formMain = QtWidgets.QWidget()
        self.ed.ui = UI.CharakterMain.Ui_formMain()
        self.ed.ui.setupUi(self.ed.formMain)
        self.ed.ui.tabs.removeTab(0)
        self.ed.ui.tabs.removeTab(0)
        self.ed.setupMainForm()
        self.savePathUpdated()
        self.ed.formMain.show()
        EventBus.doAction("charaktereditor_geoeffnet", { "neu" : True, "filepath" : "" })

    def editExisting(self):
        '''
        Creates a CharakterEditor for an existing character and shows it.
        '''
        if os.path.isdir(Wolke.Settings['Pfad-Chars']):
            startDir = Wolke.Settings['Pfad-Chars']
        else:
            startDir = ""
        spath, _ = QtWidgets.QFileDialog.getOpenFileName(None,"Charakter laden...",startDir,"XML-Datei (*.xml)")
        if spath == "":
            return
        if not spath.endswith(".xml"):
            spath = spath + ".xml"
        try:
            EventBus.doAction("charaktereditor_oeffnet", { "neu" : False, "filepath" : spath })
            self.ed = CharakterEditor.Editor(self._plugins, self.savePathUpdated, spath)
        except Exception as e:
            logging.error("Sephrasto Fehlercode " + str(Wolke.Fehlercode) + ". Exception: " + str(e))
            infoBox = QtWidgets.QMessageBox()
            infoBox.setIcon(QtWidgets.QMessageBox.Information)
            if Wolke.Fehlercode <= -40 and Wolke.Fehlercode > -80:
                infoBox.setText("Charakterdatei öffnen fehlgeschlagen")
                infoBox.setInformativeText("Die XML-Datei konnte nicht gelesen werden.\n\
Fehlercode: " + str(Wolke.Fehlercode) + "\n\
Fehlermeldung: " + Wolke.ErrorCode[Wolke.Fehlercode] + "\n")
                infoBox.setWindowTitle("Fehlerhafte Datei")
            else:
                infoBox.setText("Ein unerwarteter Fehler ist aufgetreten!")
                infoBox.setInformativeText("Ein Fehler ist aufgetreten. Versuche, Sephrasto neu zu starten?\n\
Fehlercode: " + str(Wolke.Fehlercode) + "\n")
                infoBox.setWindowTitle("Unbekannter Fehler")
            infoBox.setStandardButtons(QtWidgets.QMessageBox.Ok)
            infoBox.setEscapeButton(QtWidgets.QMessageBox.Close)
            infoBox.exec_()
        else:
            if self.ed.noDatabase:
                raise Exception("Konnte datenbank.xml nicht finden")
            self.ed.formMain = QtWidgets.QWidget()
            self.ed.ui = UI.CharakterMain.Ui_formMain()
            self.ed.ui.setupUi(self.ed.formMain)
            self.ed.ui.tabs.removeTab(0)
            self.ed.ui.tabs.removeTab(0)
            self.ed.setupMainForm()
            self.savePathUpdated()
            self.ed.formMain.show()
            EventBus.doAction("charaktereditor_geoeffnet", { "neu" : False, "filepath" : spath })

    def editRuleset(self):
        '''
        Creates the DatenbankEdit Form and shows the contents of datenbank.xml.
        '''
        self.D = DatenbankEdit.DatenbankEdit(self._plugins)
        self.D.Form = QtWidgets.QWidget()
        self.D.ui = UI.DatenbankMain.Ui_Form()
        self.D.ui.setupUi(self.D.Form)
        self.D.setupGUI()
        self.D.Form.show()

    def editSettings(self):
        EinstellungenWrapper(self._plugins)

    def help(self):
        if not hasattr(self, "hilfe"):
            self.hilfe = HilfeWrapper()
        else:
            self.hilfe.form.show()
            self.hilfe.form.activateWindow()

    def savePathUpdated(self):
        file = " - Neuer Charakter"
        if self.ed.savepath:
            file = " - " + os.path.basename(self.ed.savepath)
        rules = ""
        if Wolke.DB.datei:
           rules = " (" + os.path.splitext(os.path.basename(Wolke.DB.datei))[0] + ")"
        self.ed.formMain.setWindowTitle("Sephrasto" + file + rules)

    def updateTheme(self):
        style = Wolke.Settings['Theme']
        if style == "Standard":
            self.app.setStyle("windowsvista")
            self.app.setStyleSheet("""
            *[readOnly=\"true\"]
            {
                background-color: #FFFFFF;
                border: none
            }
            QAbstractScrollArea #scrollAreaWidgetContents
            {
                background-color: #FFFFFF
            }
            """)
            self.app.setPalette(self.app.style().standardPalette())
            QToolTip.setPalette(self.app.style().standardPalette())
        elif style == "Fusion Light":
            self.app.setStyle('fusion')
            self.app.setStyleSheet("""
            *[readOnly=\"true\"]
            {
                background-color: #FFFFFF;
                border: none
            }
            QAbstractScrollArea #scrollAreaWidgetContents
            {
                background-color: #FFFFFF
            }
            """)
            palette = self.app.style().standardPalette()
            palette.setColor(QPalette.ToolTipBase, QtCore.Qt.white)
            palette.setColor(QPalette.ToolTipText, QtCore.Qt.black)
            self.app.setPalette(palette)
            QToolTip.setPalette(palette)
        elif style == "Fusion Dark":
            self.app.setStyle('fusion')
            self.app.setStyleSheet("""
            *[readOnly=\"true\"]
            {
                background-color: #434343;
                border: none
            }
            QAbstractScrollArea
            {
                background-color: #434343;
            }
            """)
            palette = QPalette()
            palette.setColor(QPalette.Window, QColor(53, 53, 53))
            palette.setColor(QPalette.WindowText, QtCore.Qt.white)
            palette.setColor(QPalette.Base, QColor(25, 25, 25))
            palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
            palette.setColor(QPalette.ToolTipBase, QtCore.Qt.black)
            palette.setColor(QPalette.ToolTipText, QtCore.Qt.white)
            palette.setColor(QPalette.Text, QtCore.Qt.white)
            palette.setColor(QPalette.Button, QColor(53, 53, 53))
            palette.setColor(QPalette.ButtonText, QtCore.Qt.white)
            palette.setColor(QPalette.BrightText, QtCore.Qt.red)
            palette.setColor(QPalette.Link, QColor(42, 130, 218))
            palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
            palette.setColor(QPalette.HighlightedText, QtCore.Qt.black)
            palette.setColor(QPalette.Active, QPalette.Button, QColor(53, 53, 53))
            palette.setColor(QPalette.Disabled, QPalette.ButtonText, QtCore.Qt.darkGray)
            palette.setColor(QPalette.Disabled, QPalette.WindowText, QtCore.Qt.darkGray)
            palette.setColor(QPalette.Disabled, QPalette.Text, QtCore.Qt.darkGray)
            palette.setColor(QPalette.Disabled, QPalette.HighlightedText, QtCore.Qt.darkGray)
            palette.setColor(QPalette.Disabled, QPalette.Base, QColor(53, 53, 53))
            self.app.setPalette(palette)
            QToolTip.setPalette(palette)
        elif style == "DSA Forum":
            self.app.setStyle('fusion')
            self.app.setStyleSheet("""
            *[readOnly=\"true\"]
            {
                background-color: #dcc484;
                border: none;
            }
            QAbstractScrollArea
            {
                background-color: #f1e2c3;
            }
            """)
            palette = QPalette()
            # DSA-Forum colors (picked from screenshot)
            colors = {
                "bg_light": QColor("#f6efe2"),
                "bg": QColor("#f1e2c3"),
                "bg_dark": QColor("#cdad74"),  # bg braun (hover tabs)
                "button": QColor("#dcc484"),  # tabs buttons rahmen
                "text": QColor("#000000"),
                "text_dark": QColor("#7a561c"),
                "alert": QColor("#ae0007"),  # text rot
            }
            palette.setColor(QPalette.Window, colors["bg"])
            palette.setColor(QPalette.WindowText, colors["text"])
            palette.setColor(QPalette.Base, colors["bg"])
            palette.setColor(QPalette.AlternateBase, colors["bg_light"])
            palette.setColor(QPalette.ToolTipBase, colors["bg"])
            palette.setColor(QPalette.ToolTipText, colors["text"])
            palette.setColor(QPalette.Text, colors["text"])
            palette.setColor(QPalette.Button, colors["button"])
            palette.setColor(QPalette.ButtonText, colors["text"])
            palette.setColor(QPalette.BrightText, colors["alert"])
            palette.setColor(QPalette.Link, colors["text_dark"])
            palette.setColor(QPalette.Highlight, colors["bg_dark"])
            palette.setColor(QPalette.HighlightedText, colors["text"])
            #palette.setColor(QPalette.Active, QPalette.Button, colors["alert"])
            palette.setColor(QPalette.Disabled, QPalette.ButtonText, colors["text_dark"])
            palette.setColor(QPalette.Disabled, QPalette.WindowText, colors["text_dark"])
            palette.setColor(QPalette.Disabled, QPalette.Text, colors["text_dark"])
            palette.setColor(QPalette.Disabled, QPalette.HighlightedText, colors["text_dark"])
            palette.setColor(QPalette.Disabled, QPalette.Base, colors["bg_dark"])
            self.app.setPalette(palette)
            QToolTip.setPalette(palette)

if __name__ == "__main__":
    itm = MainWindowWrapper()<commit_msg>PluginAPI: action charaktereditor_oeffnet is now called consistently, independent of opening a new or existing character.<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 23 21:30:34 2017

@author: Aeolitus
"""
from PyQt5 import QtWidgets, QtCore, QtGui
import sys
import logging
import os
import os.path
import UI.MainWindow
import CharakterEditor
import DatenbankEdit
import UI.CharakterMain
import UI.DatenbankMain
from Wolke import Wolke
import yaml
from EinstellungenWrapper import EinstellungenWrapper
from HilfeWrapper import HilfeWrapper
import Version
from EventBus import EventBus
from PluginLoader import PluginLoader
from UpdateChecker import UpdateChecker
from PyQt5.QtGui import QPalette, QColor
from PyQt5.QtWidgets import QToolTip

loglevels = {0: logging.ERROR, 1: logging.WARNING, 2: logging.DEBUG}
logging.basicConfig(filename="sephrasto.log", \
    level=loglevels[Wolke.Settings['Logging']], \
    format="%(asctime)s | %(levelname)s | %(filename)s::%(funcName)s(%(lineno)d) | %(message)s")

def sephrasto_excepthook(exc_type, exc_value, tb):
    traceback = [' Traceback (most recent call last):']
    while tb:
        filename = tb.tb_frame.f_code.co_filename
        name = tb.tb_frame.f_code.co_name
        lineno = tb.tb_lineno
        traceback.append('   File "%.500s", line %d, in %.500s' %(filename, lineno, name))
        tb = tb.tb_next

    # Exception type and value
    exception = ' %s: %s' %(exc_type.__name__, exc_value)
    logging.critical(exception + "\n".join(traceback))

    #Try to show message box, hopefully its not a crash in Qt
    messagebox = QtWidgets.QMessageBox()
    messagebox.setWindowTitle("Fehler!")
    messagebox.setText("Unerwarteter Fehler:" + exception + ". Bei Fragen zum diesem Fehler bitte sephrasto.log mitsenden.")
    messagebox.setIcon(QtWidgets.QMessageBox.Critical)
    messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
    messagebox.exec_()

class PluginData(object):
    def __init__(self, name, description, isOfficial, plugin):
        super().__init__()
        self.name = name
        self.description = description
        self.isOfficial = isOfficial
        self.plugin = plugin

class MainWindowWrapper(object):
    '''
    Main Class responsible for running the entire application.
    Just shows three buttons and handles the execution of the individual subparts.
    '''
    def __init__(self):
        sys.excepthook = sephrasto_excepthook

        '''
        Initializes the GUI and connects the buttons.
        '''
        self._version_ = "v" + str(Version._sephrasto_version_major) + "." + str(Version._sephrasto_version_minor) + "." + str(Version._sephrasto_version_build)
        logging.critical("Starte Sephrasto " + self._version_) #critical so it's always printed, independent of the debug level setting

        super().__init__()

        os.chdir(os.path.dirname(os.path.abspath(__file__)))

        #Make sure the application scales properly, i.e. in Win10 users can change the UI scale in the display settings
        if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'):
            QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
        if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
            QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)

        self.app = QtCore.QCoreApplication.instance()
        if self.app is None:
            self.app = QtWidgets.QApplication(sys.argv)

        self.Form = QtWidgets.QWidget()
        self.ui = UI.MainWindow.Ui_Form()
        self.ui.setupUi(self.Form)
        self.ui.buttonNew.clicked.connect(self.createNew)
        self.ui.buttonEdit.clicked.connect(self.editExisting)
        self.ui.buttonRules.clicked.connect(self.editRuleset)
        self.ui.buttonSettings.clicked.connect(self.editSettings)
        self.ui.buttonHelp.clicked.connect(self.help)
        self.ui.labelVersion.setText(self._version_ + " - by Aeolitus ")

        self.app.setWindowIcon(QtGui.QIcon('icon_large.png'))

        # Get the Settings loaded
        EinstellungenWrapper.load()
        logging.getLogger().setLevel(loglevels[Wolke.Settings['Logging']])

        self.updateTheme()

        UpdateChecker.checkForUpdate()

        self._plugins = []
        pluginPaths = ["Plugins", Wolke.Settings['Pfad-Plugins']]

        for pluginPath in pluginPaths:
            pluginNames = PluginLoader.getPlugins(pluginPath)

            for pluginName in pluginNames:
                description = PluginLoader.getPluginDescription(pluginPath, pluginName)
                if pluginName in Wolke.Settings['Deaktivierte-Plugins']:
                    self._plugins.append(PluginData(pluginName, description, pluginPath == "Plugins", None))
                    continue

                plugin = PluginLoader.loadPlugin(pluginPath, pluginName)
                self._plugins.append(PluginData(pluginName, description, pluginPath == "Plugins", plugin))
                logging.critical("Plugin: loaded " + pluginName)
                if hasattr(plugin, "createMainWindowButtons"):
                    for button in plugin.createMainWindowButtons():
                        button.setParent(self.Form)
                        button.setMinimumSize(QtCore.QSize(0, 25))
                        self.ui.vlPluginButtons.addWidget(button)

        EventBus.doAction("plugins_geladen")

        EventBus.addAction("charaktereditor_reload", self.charakterEditorReloadHook)
        EventBus.addAction("charaktereditor_modified", self.charakterEditorModifiedHook)

        self.Form.show()
        sys.exit(self.app.exec_())

    def charakterEditorReloadHook(self, params):
        if self.ed and not self.ed.formMain.isHidden():
            self.ed.reloadAll()

    def charakterEditorModifiedHook(self, params):
        if hasattr(self, "ed") and self.ed and not self.ed.formMain.isHidden():
            self.ed.onModified()

    def createNew(self):
        '''
        Creates a new CharakterEditor which is empty and shows it.
        '''
        EventBus.doAction("charaktereditor_oeffnet", { "neu" : True, "filepath" : "" })
        self.ed = CharakterEditor.Editor(self._plugins, self.savePathUpdated)
        if self.ed.noDatabase:
            raise Exception("Konnte datenbank.xml nicht finden")
        self.ed.formMain = QtWidgets.QWidget()
        self.ed.ui = UI.CharakterMain.Ui_formMain()
        self.ed.ui.setupUi(self.ed.formMain)
        self.ed.ui.tabs.removeTab(0)
        self.ed.ui.tabs.removeTab(0)
        self.ed.setupMainForm()
        self.savePathUpdated()
        self.ed.formMain.show()
        EventBus.doAction("charaktereditor_geoeffnet", { "neu" : True, "filepath" : "" })

    def editExisting(self):
        '''
        Creates a CharakterEditor for an existing character and shows it.
        '''
        if os.path.isdir(Wolke.Settings['Pfad-Chars']):
            startDir = Wolke.Settings['Pfad-Chars']
        else:
            startDir = ""
        spath, _ = QtWidgets.QFileDialog.getOpenFileName(None,"Charakter laden...",startDir,"XML-Datei (*.xml)")
        if spath == "":
            return
        if not spath.endswith(".xml"):
            spath = spath + ".xml"
        try:
            EventBus.doAction("charaktereditor_oeffnet", { "neu" : False, "filepath" : spath })
            self.ed = CharakterEditor.Editor(self._plugins, self.savePathUpdated, spath)
        except Exception as e:
            logging.error("Sephrasto Fehlercode " + str(Wolke.Fehlercode) + ". Exception: " + str(e))
            infoBox = QtWidgets.QMessageBox()
            infoBox.setIcon(QtWidgets.QMessageBox.Information)
            if Wolke.Fehlercode <= -40 and Wolke.Fehlercode > -80:
                infoBox.setText("Charakterdatei öffnen fehlgeschlagen")
                infoBox.setInformativeText("Die XML-Datei konnte nicht gelesen werden.\n\
Fehlercode: " + str(Wolke.Fehlercode) + "\n\
Fehlermeldung: " + Wolke.ErrorCode[Wolke.Fehlercode] + "\n")
                infoBox.setWindowTitle("Fehlerhafte Datei")
            else:
                infoBox.setText("Ein unerwarteter Fehler ist aufgetreten!")
                infoBox.setInformativeText("Ein Fehler ist aufgetreten. Versuche, Sephrasto neu zu starten?\n\
Fehlercode: " + str(Wolke.Fehlercode) + "\n")
                infoBox.setWindowTitle("Unbekannter Fehler")
            infoBox.setStandardButtons(QtWidgets.QMessageBox.Ok)
            infoBox.setEscapeButton(QtWidgets.QMessageBox.Close)
            infoBox.exec_()
        else:
            if self.ed.noDatabase:
                raise Exception("Konnte datenbank.xml nicht finden")
            self.ed.formMain = QtWidgets.QWidget()
            self.ed.ui = UI.CharakterMain.Ui_formMain()
            self.ed.ui.setupUi(self.ed.formMain)
            self.ed.ui.tabs.removeTab(0)
            self.ed.ui.tabs.removeTab(0)
            self.ed.setupMainForm()
            self.savePathUpdated()
            self.ed.formMain.show()
            EventBus.doAction("charaktereditor_geoeffnet", { "neu" : False, "filepath" : spath })

    def editRuleset(self):
        '''
        Creates the DatenbankEdit Form and shows the contents of datenbank.xml.
        '''
        self.D = DatenbankEdit.DatenbankEdit(self._plugins)
        self.D.Form = QtWidgets.QWidget()
        self.D.ui = UI.DatenbankMain.Ui_Form()
        self.D.ui.setupUi(self.D.Form)
        self.D.setupGUI()
        self.D.Form.show()

    def editSettings(self):
        EinstellungenWrapper(self._plugins)

    def help(self):
        if not hasattr(self, "hilfe"):
            self.hilfe = HilfeWrapper()
        else:
            self.hilfe.form.show()
            self.hilfe.form.activateWindow()

    def savePathUpdated(self):
        file = " - Neuer Charakter"
        if self.ed.savepath:
            file = " - " + os.path.basename(self.ed.savepath)
        rules = ""
        if Wolke.DB.datei:
           rules = " (" + os.path.splitext(os.path.basename(Wolke.DB.datei))[0] + ")"
        self.ed.formMain.setWindowTitle("Sephrasto" + file + rules)

    def updateTheme(self):
        style = Wolke.Settings['Theme']
        if style == "Standard":
            self.app.setStyle("windowsvista")
            self.app.setStyleSheet("""
            *[readOnly=\"true\"]
            {
                background-color: #FFFFFF;
                border: none
            }
            QAbstractScrollArea #scrollAreaWidgetContents
            {
                background-color: #FFFFFF
            }
            """)
            self.app.setPalette(self.app.style().standardPalette())
            QToolTip.setPalette(self.app.style().standardPalette())
        elif style == "Fusion Light":
            self.app.setStyle('fusion')
            self.app.setStyleSheet("""
            *[readOnly=\"true\"]
            {
                background-color: #FFFFFF;
                border: none
            }
            QAbstractScrollArea #scrollAreaWidgetContents
            {
                background-color: #FFFFFF
            }
            """)
            palette = self.app.style().standardPalette()
            palette.setColor(QPalette.ToolTipBase, QtCore.Qt.white)
            palette.setColor(QPalette.ToolTipText, QtCore.Qt.black)
            self.app.setPalette(palette)
            QToolTip.setPalette(palette)
        elif style == "Fusion Dark":
            self.app.setStyle('fusion')
            self.app.setStyleSheet("""
            *[readOnly=\"true\"]
            {
                background-color: #434343;
                border: none
            }
            QAbstractScrollArea
            {
                background-color: #434343;
            }
            """)
            palette = QPalette()
            palette.setColor(QPalette.Window, QColor(53, 53, 53))
            palette.setColor(QPalette.WindowText, QtCore.Qt.white)
            palette.setColor(QPalette.Base, QColor(25, 25, 25))
            palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
            palette.setColor(QPalette.ToolTipBase, QtCore.Qt.black)
            palette.setColor(QPalette.ToolTipText, QtCore.Qt.white)
            palette.setColor(QPalette.Text, QtCore.Qt.white)
            palette.setColor(QPalette.Button, QColor(53, 53, 53))
            palette.setColor(QPalette.ButtonText, QtCore.Qt.white)
            palette.setColor(QPalette.BrightText, QtCore.Qt.red)
            palette.setColor(QPalette.Link, QColor(42, 130, 218))
            palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
            palette.setColor(QPalette.HighlightedText, QtCore.Qt.black)
            palette.setColor(QPalette.Active, QPalette.Button, QColor(53, 53, 53))
            palette.setColor(QPalette.Disabled, QPalette.ButtonText, QtCore.Qt.darkGray)
            palette.setColor(QPalette.Disabled, QPalette.WindowText, QtCore.Qt.darkGray)
            palette.setColor(QPalette.Disabled, QPalette.Text, QtCore.Qt.darkGray)
            palette.setColor(QPalette.Disabled, QPalette.HighlightedText, QtCore.Qt.darkGray)
            palette.setColor(QPalette.Disabled, QPalette.Base, QColor(53, 53, 53))
            self.app.setPalette(palette)
            QToolTip.setPalette(palette)
        elif style == "DSA Forum":
            self.app.setStyle('fusion')
            self.app.setStyleSheet("""
            *[readOnly=\"true\"]
            {
                background-color: #dcc484;
                border: none;
            }
            QAbstractScrollArea
            {
                background-color: #f1e2c3;
            }
            """)
            palette = QPalette()
            # DSA-Forum colors (picked from screenshot)
            colors = {
                "bg_light": QColor("#f6efe2"),
                "bg": QColor("#f1e2c3"),
                "bg_dark": QColor("#cdad74"),  # bg braun (hover tabs)
                "button": QColor("#dcc484"),  # tabs buttons rahmen
                "text": QColor("#000000"),
                "text_dark": QColor("#7a561c"),
                "alert": QColor("#ae0007"),  # text rot
            }
            palette.setColor(QPalette.Window, colors["bg"])
            palette.setColor(QPalette.WindowText, colors["text"])
            palette.setColor(QPalette.Base, colors["bg"])
            palette.setColor(QPalette.AlternateBase, colors["bg_light"])
            palette.setColor(QPalette.ToolTipBase, colors["bg"])
            palette.setColor(QPalette.ToolTipText, colors["text"])
            palette.setColor(QPalette.Text, colors["text"])
            palette.setColor(QPalette.Button, colors["button"])
            palette.setColor(QPalette.ButtonText, colors["text"])
            palette.setColor(QPalette.BrightText, colors["alert"])
            palette.setColor(QPalette.Link, colors["text_dark"])
            palette.setColor(QPalette.Highlight, colors["bg_dark"])
            palette.setColor(QPalette.HighlightedText, colors["text"])
            #palette.setColor(QPalette.Active, QPalette.Button, colors["alert"])
            palette.setColor(QPalette.Disabled, QPalette.ButtonText, colors["text_dark"])
            palette.setColor(QPalette.Disabled, QPalette.WindowText, colors["text_dark"])
            palette.setColor(QPalette.Disabled, QPalette.Text, colors["text_dark"])
            palette.setColor(QPalette.Disabled, QPalette.HighlightedText, colors["text_dark"])
            palette.setColor(QPalette.Disabled, QPalette.Base, colors["bg_dark"])
            self.app.setPalette(palette)
            QToolTip.setPalette(palette)

if __name__ == "__main__":
    itm = MainWindowWrapper()<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

import salt.modules.config

salt.modules.config.__pillar__ = {}
salt.modules.config.__grains__ = {}

__salt__ = {'config.get': salt.modules.config.get}

def configgrains():
    '''
    Given a list of config values, create custom grains with custom names.
    The list comes from config.
    '''
    grains = {}
    salt.modules.config.__opts__ = __opts__

    grains_to_make = __salt__['config.get']('hubblestack:grains', default=[])
    for grain in grains_to_make:
        for k, v in grain.iteritems():
            grain_value = __salt__['config.get'](v, default=None)
            if grain_value:
                grains[k] = grain_value
    return grains
 <commit_msg>Extra space<commit_after># -*- coding: utf-8 -*-

import salt.modules.config

salt.modules.config.__pillar__ = {}
salt.modules.config.__grains__ = {}

__salt__ = {'config.get': salt.modules.config.get}

def configgrains():
    '''
    Given a list of config values, create custom grains with custom names.
    The list comes from config.
    '''
    grains = {}
    salt.modules.config.__opts__ = __opts__

    grains_to_make = __salt__['config.get']('hubblestack:grains', default=[])
    for grain in grains_to_make:
        for k, v in grain.iteritems():
            grain_value = __salt__['config.get'](v, default=None)
            if grain_value:
                grains[k] = grain_value
    return grains
<|endoftext|>


<commit_before>#!/usr/bin/python
import os
import sys
import shutil
import filecmp
import subprocess
import zlib
import multiprocessing

"""
    Directory-wide diff and patch.
    Patch files are 7z archives containing an index file and a files directory.
    The index file has a list of modified files. Each entry consists of an operation
    (A/M/D = Added/Modified/Deleted), the Adler32 checksums of the old and the new version
    and the relative path to the file.
    The files directory contains all added files and, for all modified files, the bsdiff
    patches that can convert the old to the new version. The directory structure within
    the files directory is the same as in the target directory.

    Creating a patch can be multithreaded to make use of multiple cpu cores.
    To avoid conflicts, each process writes to its own index file, they are merged at the end.

    Requires the command-line version of 7zip and the Windows version of bsdiff/bspatch.
"""

SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
SEVENZIP_EXE = os.path.join(SCRIPT_DIR, '7zip', 'x64', '7za.exe')
BSDIFF_EXE = os.path.join(SCRIPT_DIR, 'bsdiff', 'bsdiff.exe')
BSPATCH_EXE = os.path.join(SCRIPT_DIR, 'bsdiff', 'bspatch.exe')
VERBOSITY_LEVEL = 0
NUM_WORKERS = 1

def create_patch(oldDir, newDir, outDir):
    patchDir = os.path.join(outDir, 'patch_temp')
    if not os.path.exists(patchDir):
        os.mkdir(patchDir)
    elif not is_empty_directory(patchDir):
        print 'patch_temp directory is not empty! Aborting.'
        return False

    if validate_environment():
        walk_old_dir(oldDir, newDir, patchDir)
        walk_new_dir(oldDir, newDir, patchDir)
        merge_index(patchDir)
        zip_directory(patchDir, patchDir + '.7z')

def apply_patch(patchFilePath, targetDir):
    baseDir = os.path.dirname(patchFilePath)
    patchDir = os.path.join(baseDir, 'patch_temp')
    if validate_environment():
        unzip_directory(patchFilePath, os.path.dirname(patchDir))
        index = read_index(patchDir)
        for (operation, path, checksumOld, checksumNew) in index:
            if (operation != 'A'):
                validate_checksum_pre(os.path.join(targetDir, path), checksumOld)
        for (operation, path) in index:
            apply_file_operation(operation, path, patchDir, targetDir)
        for (operation, path, checksumOld, checksumNew) in index:
            if (operation != 'D'):
                validate_checksum_post(os.path.join(targetDir, path), checksumNew)

    shutil.rmtree(patchDir)

def apply_file_operation(operation, relPath, patchDir, targetDir):
    srcPath = os.path.join(patchDir, 'files', relPath)
    dstPath = os.path.join(targetDir, relPath)

    if operation == 'A':
        add_file(srcPath, dstPath)
    if operation == 'M':
        modify_file(srcPath, dstPath)
    if operation == 'D':
        delete_file(dstPath)

def walk_dir(rootPath, oldDir, newDir, patchDir):
    indexPath = os.path.join(patchDir, 'index')
    for (dirpath, dirnames, filenames) in os.walk(rootPath):
        relDir = os.path.relpath(dirpath, rootPath)
        for filename in filenames:
            relPath = os.path.join(relDir, filename)
            oldPath = os.path.join(oldDir, relPath)
            newPath = os.path.join(newDir, relPath)
            patchPath = os.path.join(patchDir, 'files', relPath)
            yield (relPath, oldPath, newPath, patchPath, indexPath)

def walk_old_dir(oldDir, newDir, patchDir):
    """Traverse <oldDir> and index all files that are modified or deleted in <newDir>"""
    print ''
    print 'Checking Old Files in ' + oldDir
    if NUM_WORKERS > 1:
        pool = multiprocessing.Pool(processes=NUM_WORKERS)
        pool.map(visit_old_file, walk_dir(oldDir, oldDir, newDir, patchDir))
    else:
        map(visit_old_file, walk_dir(oldDir, oldDir, newDir, patchDir))

def visit_old_file((relPath, oldPath, newPath, patchPath, indexPath)):
    print_verbose(2, '    ' + oldPath)

    if not os.path.exists(newPath):
        add_to_index('D', relPath, indexPath)
        return

    if not filecmp.cmp(oldPath, newPath):
        mkdir_if_not_exists(os.path.dirname(patchPath))
        bsdiff(oldPath, newPath, patchPath)
        chkOld = checksum(oldPath)
        chkNew = checksum(newPath)
        add_to_index('M', relPath, indexPath, chkOld, chkNew)
        return

def walk_new_dir(oldDir, newDir, patchDir):
    """Traverse <newDir> and index all files as added that don't appear in <oldDir>"""
    print 'Checking for new files...'
    for (relPath, oldPath, newPath, patchPath, indexPath) in walk_dir(newDir, oldDir, newDir, patchDir):
        visit_new_file(relPath, oldPath, newPath, patchPath, indexPath)

def visit_new_file(relPath, oldPath, newPath, patchPath, indexPath):
        print_verbose(2, '    ' + relPath)
        if not os.path.exists(oldPath):
            targetDir = os.path.dirname(patchPath)
            mkdir_if_not_exists(targetDir)
            shutil.copy(newPath, targetDir)
            add_to_index('A', relPath, indexPath)

def add_file(srcPath, dstPath):
    dstDir = os.path.dirname(dstPath)
    if not os.path.exists(dstDir):
        os.makedirs(dstDir)
    os.rename(srcPath, dstPath)

def modify_file(filePath, patchFile):
    bspatch(filePath, filePath, patchFile)

def delete_file(path):
    os.remove(path)

def bsdiff(oldFile, newFile, patchFile):
    """Creates a binary diff between <oldFile> and <newFile> and stores it in <patchFile>"""
    global BSDIFF_EXE
    subprocess.call([BSDIFF_EXE, oldFile, newFile, patchFile])

def bspatch(oldFile, newFile, patchFile):
    """Applies the <patchFile> to the <oldFile> and writes the result to <newFile>"""
    global BSPATCH_EXE
    subprocess.call([BSPATCH_EXE, oldFile, newFile, patchFile])

def add_to_index(operation, path, indexPath, checksumOld=0, checksumNew=0):
    """Adds an entry to the index. Each process has its own index file.
        They must be merged with merge_index() after all processes are done."""
    indexPath = indexPath + '.' + str(os.getpid())
    line = operation + ' ' + str(checksumOld) + ' ' + str(checksumNew) + ' ' + path
    print_verbose(1, line)
    with open(indexPath, 'a') as indexFile:
        indexFile.write(unicode(line + '\n'))

def merge_index(patchDir):
    """Merges the index files of the separate worker processes into one."""
    indexFiles = [ os.path.join(patchDir,f) \
                       for f in os.listdir(patchDir) \
                           if os.path.isfile(os.path.join(patchDir,f)) \
                           and f.startswith('index.') ]
    indexFile = os.path.join(patchDir, 'index')
    with open(indexFile, 'w') as index:
        for partialIndexFile in indexFiles:
            with open(partialIndexFile, 'r') as partialIndex:
                index.write(partialIndex.read())
            os.remove(partialIndexFile)

def read_index(patchDir):
    """Read and parse the index file. Returns a list of
        (operation, path, checksumOld, checksumNew) tuples"""
    indexPath = os.path.join(patchDir, 'index')
    with open(indexPath, 'r') as indexFile:
        result = []
        for line in indexFile.readlines():
            parts = line.split(4)
            operation = parts[0]
            checksumOld = int(parts[1])
            checksumNew = int(parts[2])
            path = parts[3]
            result.append( (operation, path, checksumOld, checksumNew) )
    return result

def zip_directory(directory, zipPath):
    """Creates a 7z archive at <zipPath> containing the files from <directory>."""
    global SEVENZIP_EXE
    subprocess.call([SEVENZIP_EXE, 'a', zipPath, directory, '-mx9', '-t7z'])

def unzip_directory(zipPath, directory):
    """Extracts the 7z archive <zipPath> and puts the content into directory <directory>"""
    global SEVENZIP_EXE
    subprocess.call([SEVENZIP_EXE, 'x', zipPath, '-o' + directory])

def checksum(path):
    with open(path, 'r') as f:
        return zlib.adler32(f.read())

class ChecksumException(Exception):
    def __init__(self, path):
        self.path = path

    def msg(self):
        return 'Cannot apply patch because file at ' + self.path + \
            ' does not have the correct checksum. Please reinstall the full release.'

def validate_checksum_pre(path, expectedChecksum):
    if checksum(path) != expectedChecksum:
        raise ChecksumException(path)

def validate_checksum_post(path, expectedChecksum):
    if checksum(path) != expectedChecksum:
        print 'WARNING: File ' + path + ' is corrupted! Please reinstall the full release.'

def validate_environment():
    global BSDIFF_EXE
    global BSPATCH_EXE
    global SEVENZIP_EXE
    if not os.path.exists(BSDIFF_EXE):
        print "Couldn't find bsdiff"
        print "Please download from http://sites.inka.de/tesla/download/bsdiff4.3-win32.zip"
        return False
    if not os.path.exists(BSPATCH_EXE):
        print "Couldn't find bspatch"
        print "Please download from http://sites.inka.de/tesla/download/bsdiff4.3-win32.zip"
        return False
    if not os.path.exists(SEVENZIP_EXE):
        print "Couldn't find 7zip"
        print "Please download from http://www.7-zip.org/a/7z1507-extra.7z"
        return False

    return True

def mkdir_if_not_exists(path):
    if not os.path.exists(path):
        try:
            os.makedirs(path)
        except:
            if not os.path.exists(path):
                raise Exception("cannot create directory " + path)

def is_empty_directory(path):
    if not os.path.isdir(path):
        return False
    if len(os.listdir(path)) > 0:
        return False
    return True

def print_verbose(verbosity, msg):
    if verbosity <= VERBOSITY_LEVEL:
        print msg

def _parseExtraArgs(i):
    global VERBOSITY_LEVEL
    global NUM_WORKERS
    if i < len(sys.argv):
        if sys.argv[i] == '-v':
            VERBOSITY_LEVEL = 1
            print 'Verbosity Level ' + str(VERBOSITY_LEVEL)
        elif sys.argv[i] == '-vv':
            VERBOSITY_LEVEL = 2
            print 'Verbosity Level ' + str(VERBOSITY_LEVEL)
        elif sys.argv[i][0:2] == '-j':
            NUM_WORKERS = int(sys.argv[i][2:])
            print 'Workers: ' + str(NUM_WORKERS)
        else:
            print 'unrecognized argument ' + sys.argv[i]
            usage()
        _parseExtraArgs(i+1)

def parseExtraArgs(i):
    _parseExtraArgs(i)
    if VERBOSITY_LEVEL > 0 and NUM_WORKERS > 1:
        print 'WARNING: There will be no verbose log output when using multiple workers.'

def usage():
    print 'Wrong arguments. Usage:'
    print '    patch.py diff <oldDir> <newDir> <outDir> [switch args]'
    print 'or'
    print '    patch.py patch <patchFile> <targetDir> [switch args]'
    print ''
    print 'Switch Args: '
    print '-v   Print more status messages'
    print '-vv  Print a lot of status messages (only for debugging)'
    print '-j#  Parallel processing. Replace # with the number of desired worker threads'
    sys.exit(1)

if __name__ == '__main__':
    if len(sys.argv) < 2:
        usage()

    operation = sys.argv[1]
    if operation == 'diff':
        if len(sys.argv) < 5:
            usage()
        oldDir = sys.argv[2]
        newDir = sys.argv[3]
        patchDir = sys.argv[4]
        parseExtraArgs(5)
        create_patch(oldDir, newDir, patchDir)

    elif operation == 'patch':
        if len(sys.argv) < 4:
            usage()
        patchFile = sys.argv[2]
        targetDir = sys.argv[3]
        parseExtraArgs(4)
        apply_patch(patchFile, targetDir)
    else:
        usage()
<commit_msg>fixed a number of bugs with applying the patch, works now<commit_after>#!/usr/bin/python
import os
import sys
import shutil
import filecmp
import subprocess
import zlib
import multiprocessing

"""
    Directory-wide diff and patch.
    Patch files are 7z archives containing an index file and a files directory.
    The index file has a list of modified files. Each entry consists of an operation
    (A/M/D = Added/Modified/Deleted), the Adler32 checksums of the old and the new version
    and the relative path to the file.
    The files directory contains all added files and, for all modified files, the bsdiff
    patches that can convert the old to the new version. The directory structure within
    the files directory is the same as in the target directory.

    Creating a patch can be multithreaded to make use of multiple cpu cores.
    To avoid conflicts, each process writes to its own index file, they are merged at the end.

    Requires the command-line version of 7zip and the Windows version of bsdiff/bspatch.
"""

SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
SEVENZIP_EXE = os.path.join(SCRIPT_DIR, '7zip', 'x64', '7za.exe')
BSDIFF_EXE = os.path.join(SCRIPT_DIR, 'bsdiff', 'bsdiff.exe')
BSPATCH_EXE = os.path.join(SCRIPT_DIR, 'bsdiff', 'bspatch.exe')
VERBOSITY_LEVEL = 0
NUM_WORKERS = 1

def create_patch(oldDir, newDir, outDir):
    patchDir = os.path.join(outDir, 'patch_temp')
    if not os.path.exists(patchDir):
        os.mkdir(patchDir)
    elif not is_empty_directory(patchDir):
        print 'patch_temp directory is not empty! Aborting.'
        return False

    if validate_environment():
        walk_old_dir(oldDir, newDir, patchDir)
        walk_new_dir(oldDir, newDir, patchDir)
        merge_index(patchDir)
        zip_directory(patchDir, patchDir + '.7z')

def apply_patch(patchFilePath, targetDir):
    baseDir = os.path.dirname(patchFilePath)
    patchDir = os.path.join(baseDir, 'patch_temp')
    if validate_environment():
        try:
            unzip_directory(patchFilePath, os.path.dirname(patchDir))
            index = read_index(patchDir)

            print 'Checking for correct version of files...'
            for (operation, path, checksumOld, checksumNew) in index:
                if (operation != 'A'):
                    print_verbose(2, path)
                    validate_checksum_pre(os.path.join(targetDir, path), checksumOld)

            print 'Applying Patch...'
            for (operation, path, checksumOld, checksumNew) in index:
                print_verbose(1, operation + ' ' + path)
                apply_file_operation(operation, path, patchDir, targetDir)

            print 'Validating Result...'
            for (operation, path, checksumOld, checksumNew) in index:
                print_verbose(2, path)
                if (operation != 'D'):
                    validate_checksum_post(os.path.join(targetDir, path), checksumNew)

        except ChecksumException as ex:
            print ex.msg()

    shutil.rmtree(patchDir)

def apply_file_operation(operation, relPath, patchDir, targetDir):
    dstPath = os.path.join(targetDir, relPath)
    patchPath = os.path.join(patchDir, 'files', relPath)

    if operation == 'A':
        add_file(patchPath, dstPath)
    if operation == 'M':
        modify_file(dstPath, patchPath)
    if operation == 'D':
        delete_file(dstPath)

def walk_dir(rootPath, oldDir, newDir, patchDir):
    indexPath = os.path.join(patchDir, 'index')
    for (dirpath, dirnames, filenames) in os.walk(rootPath):
        relDir = os.path.relpath(dirpath, rootPath)
        for filename in filenames:
            relPath = os.path.join(relDir, filename)
            oldPath = os.path.join(oldDir, relPath)
            newPath = os.path.join(newDir, relPath)
            patchPath = os.path.join(patchDir, 'files', relPath)
            yield (relPath, oldPath, newPath, patchPath, indexPath)

def walk_old_dir(oldDir, newDir, patchDir):
    """Traverse <oldDir> and index all files that are modified or deleted in <newDir>"""
    print ''
    print 'Checking Old Files in ' + oldDir
    if NUM_WORKERS > 1:
        pool = multiprocessing.Pool(processes=NUM_WORKERS)
        pool.map(visit_old_file, walk_dir(oldDir, oldDir, newDir, patchDir))
    else:
        map(visit_old_file, walk_dir(oldDir, oldDir, newDir, patchDir))

def visit_old_file((relPath, oldPath, newPath, patchPath, indexPath)):
    print_verbose(2, '    ' + oldPath)

    if not os.path.exists(newPath):
        add_to_index('D', relPath, indexPath, checksum(oldPath), 0)
        return

    if not filecmp.cmp(oldPath, newPath):
        mkdir_if_not_exists(os.path.dirname(patchPath))
        bsdiff(oldPath, newPath, patchPath)
        add_to_index('M', relPath, indexPath, checksum(oldPath), checksum(newPath))
        return

def walk_new_dir(oldDir, newDir, patchDir):
    """Traverse <newDir> and index all files as added that don't appear in <oldDir>"""
    print 'Checking for new files...'
    for (relPath, oldPath, newPath, patchPath, indexPath) in walk_dir(newDir, oldDir, newDir, patchDir):
        visit_new_file(relPath, oldPath, newPath, patchPath, indexPath)

def visit_new_file(relPath, oldPath, newPath, patchPath, indexPath):
        print_verbose(2, '    ' + relPath)
        if not os.path.exists(oldPath):
            targetDir = os.path.dirname(patchPath)
            mkdir_if_not_exists(targetDir)
            shutil.copy(newPath, targetDir)
            add_to_index('A', relPath, indexPath, 0, checksum(newPath))

def add_file(srcPath, dstPath):
    dstDir = os.path.dirname(dstPath)
    if not os.path.exists(dstDir):
        os.makedirs(dstDir)
    os.rename(srcPath, dstPath)

def modify_file(filePath, patchFile):
    tmpFilePath = filePath + '.patch_tmp'
    bspatch(filePath, tmpFilePath, patchFile)
    os.remove(filePath)
    os.rename(tmpFilePath, filePath)

def delete_file(path):
    os.remove(path)

def bsdiff(oldFile, newFile, patchFile):
    """Creates a binary diff between <oldFile> and <newFile> and stores it in <patchFile>"""
    global BSDIFF_EXE
    subprocess.call([BSDIFF_EXE, oldFile, newFile, patchFile])

def bspatch(oldFile, newFile, patchFile):
    """Applies the <patchFile> to the <oldFile> and writes the result to <newFile>"""
    global BSPATCH_EXE
    print_verbose(2, BSPATCH_EXE + ' "' + oldFile + '" "' + newFile + '" "' + patchFile + '"')
    subprocess.call([BSPATCH_EXE, oldFile, newFile, patchFile])

def add_to_index(operation, path, indexPath, checksumOld=0, checksumNew=0):
    """Adds an entry to the index. Each process has its own index file.
        They must be merged with merge_index() after all processes are done."""
    indexPath = indexPath + '.' + str(os.getpid())
    line = operation + ' ' + str(checksumOld) + ' ' + str(checksumNew) + ' ' + path
    print_verbose(1, line)
    with open(indexPath, 'a') as indexFile:
        indexFile.write(unicode(line + '\n'))

def merge_index(patchDir):
    """Merges the index files of the separate worker processes into one."""
    indexFiles = [ os.path.join(patchDir,f) \
                       for f in os.listdir(patchDir) \
                           if os.path.isfile(os.path.join(patchDir,f)) \
                           and f.startswith('index.') ]
    indexFile = os.path.join(patchDir, 'index')
    with open(indexFile, 'w') as index:
        for partialIndexFile in indexFiles:
            with open(partialIndexFile, 'r') as partialIndex:
                index.write(partialIndex.read())
            os.remove(partialIndexFile)

def read_index(patchDir):
    """Read and parse the index file. Returns a list of
        (operation, path, checksumOld, checksumNew) tuples"""
    indexPath = os.path.join(patchDir, 'index')
    with open(indexPath, 'r') as indexFile:
        result = []
        for line in indexFile.readlines():
            if len(line) == 0:
                continue
            parts = line.split(' ', 3)
            operation = parts[0]
            checksumOld = int(parts[1])
            checksumNew = int(parts[2])
            path = parts[3].strip()
            result.append( (operation, path, checksumOld, checksumNew) )
    return result

def zip_directory(directory, zipPath):
    """Creates a 7z archive at <zipPath> containing the files from <directory>."""
    global SEVENZIP_EXE
    subprocess.call([SEVENZIP_EXE, 'a', zipPath, directory, '-mx9', '-t7z'])

def unzip_directory(zipPath, directory):
    """Extracts the 7z archive <zipPath> and puts the content into directory <directory>"""
    global SEVENZIP_EXE
    subprocess.call([SEVENZIP_EXE, 'x', zipPath, '-o' + directory])

def checksum(path):
    with open(path, 'r') as f:
        return zlib.adler32(f.read())

class ChecksumException(Exception):
    def __init__(self, path, expected, actual):
        self.path = path
        self.expected = expected
        self.actual = actual

    def msg(self):
        return 'Cannot apply patch because file at ' + self.path + \
            ' does not have the correct checksum' + \
            ' (' + str(self.actual) + ' instead of ' + str(self.expected) + ').' + \
            ' Please reinstall the full release.'

def validate_checksum_pre(path, expectedChecksum):
    if checksum(path) != expectedChecksum:
        raise ChecksumException(path, expectedChecksum, checksum(path))

def validate_checksum_post(path, expectedChecksum):
    if checksum(path) != expectedChecksum:
        print 'WARNING: File ' + path + ' is corrupted! Please reinstall the full release.'

def validate_environment():
    global BSDIFF_EXE
    global BSPATCH_EXE
    global SEVENZIP_EXE
    if not os.path.exists(BSDIFF_EXE):
        print "Couldn't find bsdiff"
        print "Please download from http://sites.inka.de/tesla/download/bsdiff4.3-win32.zip"
        return False
    if not os.path.exists(BSPATCH_EXE):
        print "Couldn't find bspatch"
        print "Please download from http://sites.inka.de/tesla/download/bsdiff4.3-win32.zip"
        return False
    if not os.path.exists(SEVENZIP_EXE):
        print "Couldn't find 7zip"
        print "Please download from http://www.7-zip.org/a/7z1507-extra.7z"
        return False

    return True

def mkdir_if_not_exists(path):
    if not os.path.exists(path):
        try:
            os.makedirs(path)
        except:
            if not os.path.exists(path):
                raise Exception("cannot create directory " + path)

def is_empty_directory(path):
    if not os.path.isdir(path):
        return False
    if len(os.listdir(path)) > 0:
        return False
    return True

def print_verbose(verbosity, msg):
    if verbosity <= VERBOSITY_LEVEL:
        print msg

def _parseExtraArgs(i):
    global VERBOSITY_LEVEL
    global NUM_WORKERS
    if i < len(sys.argv):
        if sys.argv[i] == '-v':
            VERBOSITY_LEVEL = 1
            print 'Verbosity Level ' + str(VERBOSITY_LEVEL)
        elif sys.argv[i] == '-vv':
            VERBOSITY_LEVEL = 2
            print 'Verbosity Level ' + str(VERBOSITY_LEVEL)
        elif sys.argv[i][0:2] == '-j':
            NUM_WORKERS = int(sys.argv[i][2:])
            print 'Workers: ' + str(NUM_WORKERS)
        else:
            print 'unrecognized argument ' + sys.argv[i]
            usage()
        _parseExtraArgs(i+1)

def parseExtraArgs(i):
    _parseExtraArgs(i)
    if VERBOSITY_LEVEL > 0 and NUM_WORKERS > 1:
        print 'WARNING: There will be no verbose log output when using multiple workers.'

def usage():
    print 'Wrong arguments. Usage:'
    print '    patch.py diff <oldDir> <newDir> <outDir> [switch args]'
    print 'or'
    print '    patch.py patch <patchFile> <targetDir> [switch args]'
    print ''
    print 'Switch Args: '
    print '-v   Print more status messages'
    print '-vv  Print a lot of status messages (only for debugging)'
    print '-j#  Parallel processing. Replace # with the number of desired worker threads'
    sys.exit(1)

if __name__ == '__main__':
    if len(sys.argv) < 2:
        usage()

    operation = sys.argv[1]
    if operation == 'diff':
        if len(sys.argv) < 5:
            usage()
        oldDir = sys.argv[2]
        newDir = sys.argv[3]
        patchDir = sys.argv[4]
        parseExtraArgs(5)
        create_patch(oldDir, newDir, patchDir)

    elif operation == 'patch':
        if len(sys.argv) < 4:
            usage()
        patchFile = sys.argv[2]
        targetDir = sys.argv[3]
        parseExtraArgs(4)
        apply_patch(patchFile, targetDir)
    else:
        usage()
<|endoftext|>


<commit_before>"""
Color Scheme Matcher (for sublime text)
Licensed under MIT
Copyright (c) 2013 Isaac Muse <isaacmuse@gmail.com>
"""
from __future__ import absolute_import
import sublime
import re
ST3 = int(sublime.version()) >= 3000
if not ST3:
    from plistlib import readPlist
else:
    from plistlib import readPlistFromBytes
from .rgba import RGBA
from os import path

def sublime_format_path(pth):
    m = re.match(r"^([A-Za-z]{1}):(?:/|\\)(.*)", pth)
    if sublime.platform() == "windows" and m is not None:
        pth = m.group(1) + "/" + m.group(2)
    return pth.replace("\\", "/")

class ColorSchemeMatcher(object):
    def __init__(self, scheme_file, strip_trans=False, ignore_gutter=False, track_dark_background=False, filter=None):
        if filter is None:
            filter = self.filter
        self.color_scheme = path.normpath(scheme_file)
        self.scheme_file = path.basename(self.color_scheme)
        if ST3:
            self.plist_file = filter(readPlistFromBytes(sublime.load_binary_resource(sublime_format_path(self.color_scheme))))
        else:
            self.plist_file = filter(readPlist(sublime.packages_path() + self.color_scheme.replace('Packages', '')))
        self.scheme_file = scheme_file
        self.strip_trans = strip_trans
        self.ignore_gutter = ignore_gutter
        self.track_dark_background = track_dark_background
        self.dark_lumens = None
        self.matched = {}

        self.parse_scheme()

    def filter(self, plist):
        return plist

    def parse_scheme(self):
        color_settings = self.plist_file["settings"][0]["settings"]

        # Get general theme colors from color scheme file
        self.bground = self.strip_color(color_settings.get("background", '#FFFFFF'), simple_strip=True)
        self.fground = self.strip_color(color_settings.get("foreground", '#000000'))
        self.sbground = self.strip_color(color_settings.get("selection", self.fground))
        self.sfground = self.strip_color(color_settings.get("selectionForeground", None))
        self.gbground = self.strip_color(color_settings.get("gutter", self.bground)) if not self.ignore_gutter else self.bground
        self.gfground = self.strip_color(color_settings.get("gutterForeground", self.fground)) if not self.ignore_gutter else self.fground

        # Create scope colors mapping from color scheme file
        self.colors = {}
        for item in self.plist_file["settings"]:
            name = item.get('name', None)
            scope = item.get('scope', None)
            color = None
            style = []
            if 'settings' in item:
                color = item['settings'].get('foreground', None)
                bgcolor = item['settings'].get('background', None)
                if 'fontStyle' in item['settings']:
                    for s in item['settings']['fontStyle'].split(' '):
                        if s == "bold" or s == "italic":  # or s == "underline":
                            style.append(s)

            if scope is not None and name is not None and (color is not None or bgcolor is not None):
                self.colors[scope] = {
                    "name": name,
                    "color": self.strip_color(color),
                    "bgcolor": self.strip_color(bgcolor),
                    "style": style
                }

    def strip_color(self, color, simple_strip=False):
        if color is None:
            return color
        elif not self.strip_trans:
            return color.replace(" ", "")
        rgba = RGBA(color.replace(" ", ""))
        if not simple_strip:
            rgba.apply_alpha(self.bground if self.bground != "" else "#FFFFFF")
        if self.track_dark_background:
            lumens = rgba.luminance()
            if self.dark_lumens is None or lumens < self.dark_lumens:
                self.dark_lumens = lumens
        return rgba.get_rgb()

    def get_general_colors(self):
        return self.bground, self.fground, self.sbground, self.sfground, self.gbground, self.gfground

    def get_darkest_lumen(self):
        return self.dark_lumens

    def get_plist_file(self):
        return self.plist_file

    def get_scheme_file(self):
        return self.scheme_file

    def guess_color(self, view, pt, scope_key):
        color = self.fground
        bgcolor = self.bground
        style = set([])
        color_selector = "foreground"
        style_selectors = {"bold": "", "italic": ""}
        bg_selector = "background"
        if scope_key in self.matched:
            color = self.matched[scope_key]["color"]
            style = self.matched[scope_key]["style"]
            bgcolor = self.matched[scope_key]["bgcolor"]
            selectors = self.matched[scope_key]["selectors"]
            color_selector, bg_selector, style_selectors = selectors["color"], selectors["background"], selectors["style"]
        else:
            best_match_bg = 0
            best_match_fg = 0
            best_match_style = 0
            for key in self.colors:
                match = view.score_selector(pt, key)
                if self.colors[key]["color"] is not None and match > best_match_fg:
                    best_match_fg = match
                    color = self.colors[key]["color"]
                    color_selector = self.colors[key]["name"]
                if self.colors[key]["style"] is not None and match > best_match_style:
                    best_match_style = match
                    for s in self.colors[key]["style"]:
                        style.add(s)
                        if s == "bold":
                            style_selectors["bold"] = self.colors[key]["name"]
                        elif s == "italic":
                            style_selectors["italic"] = self.colors[key]["name"]
                if self.colors[key]["bgcolor"] is not None and match > best_match_bg:
                    best_match_bg = match
                    bgcolor = self.colors[key]["bgcolor"]
                    bg_selector = self.colors[key]["name"]
            self.matched[scope_key] = {
                "color": color, "bgcolor": bgcolor,
                "style": style, "selectors": {
                    "color": color_selector,
                    "background": bg_selector,
                    "style": style_selectors
                }
            }
        if len(style) == 0:
            style = "normal"
        else:
            style = ' '.join(style)
        return color, style, bgcolor, color_selector, bg_selector, style_selectors

    def shift_background_brightness(self, lumens_limit):
        dlumen = self.get_darkest_lumen()
        if dlumen is not None and dlumen < lumens_limit:
            factor = 1 + ((lumens_limit - dlumen) / 255.0)
            for k, v in self.colors.items():
                fg, bg = v["color"], v["bgcolor"]
                if fg is not None:
                    self.colors[k]["color"] = self.apply_brightness(fg, factor)
                if bg is not None:
                    self.colors[k]["bgcolor"] = self.apply_brightness(bg, factor)
            self.bground = self.apply_brightness(self.bground, factor)
            self.fground = self.apply_brightness(self.fground, factor)
            self.sbground = self.apply_brightness(self.sbground, factor)
            if self.sfground is not None:
                self.sfground = self.apply_brightness(self.sfground, factor)
            self.gbground = self.apply_brightness(self.gbground, factor)
            self.gfground = self.apply_brightness(self.gfground, factor)

    def apply_brightness(self, color, shift_factor):
        rgba = RGBA(color)
        if shift_factor is not None:
            rgba.brightness(shift_factor)
        return rgba.get_rgb()
<commit_msg>Fix: Allow ExportHtml to handle empty color fields<commit_after>"""
Color Scheme Matcher (for sublime text)
Licensed under MIT
Copyright (c) 2013 Isaac Muse <isaacmuse@gmail.com>
"""
from __future__ import absolute_import
import sublime
import re
ST3 = int(sublime.version()) >= 3000
if not ST3:
    from plistlib import readPlist
else:
    from plistlib import readPlistFromBytes
from .rgba import RGBA
from os import path

def sublime_format_path(pth):
    m = re.match(r"^([A-Za-z]{1}):(?:/|\\)(.*)", pth)
    if sublime.platform() == "windows" and m is not None:
        pth = m.group(1) + "/" + m.group(2)
    return pth.replace("\\", "/")

class ColorSchemeMatcher(object):
    def __init__(self, scheme_file, strip_trans=False, ignore_gutter=False, track_dark_background=False, filter=None):
        if filter is None:
            filter = self.filter
        self.color_scheme = path.normpath(scheme_file)
        self.scheme_file = path.basename(self.color_scheme)
        if ST3:
            self.plist_file = filter(readPlistFromBytes(sublime.load_binary_resource(sublime_format_path(self.color_scheme))))
        else:
            self.plist_file = filter(readPlist(sublime.packages_path() + self.color_scheme.replace('Packages', '')))
        self.scheme_file = scheme_file
        self.strip_trans = strip_trans
        self.ignore_gutter = ignore_gutter
        self.track_dark_background = track_dark_background
        self.dark_lumens = None
        self.matched = {}

        self.parse_scheme()

    def filter(self, plist):
        return plist

    def parse_scheme(self):
        color_settings = self.plist_file["settings"][0]["settings"]

        # Get general theme colors from color scheme file
        self.bground = self.strip_color(color_settings.get("background", '#FFFFFF'), simple_strip=True)
        self.fground = self.strip_color(color_settings.get("foreground", '#000000'))
        self.sbground = self.strip_color(color_settings.get("selection", self.fground))
        self.sfground = self.strip_color(color_settings.get("selectionForeground", None))
        self.gbground = self.strip_color(color_settings.get("gutter", self.bground)) if not self.ignore_gutter else self.bground
        self.gfground = self.strip_color(color_settings.get("gutterForeground", self.fground)) if not self.ignore_gutter else self.fground

        # Create scope colors mapping from color scheme file
        self.colors = {}
        for item in self.plist_file["settings"]:
            name = item.get('name', None)
            scope = item.get('scope', None)
            color = None
            style = []
            if 'settings' in item:
                color = item['settings'].get('foreground', None)
                bgcolor = item['settings'].get('background', None)
                if 'fontStyle' in item['settings']:
                    for s in item['settings']['fontStyle'].split(' '):
                        if s == "bold" or s == "italic":  # or s == "underline":
                            style.append(s)

            if scope is not None and name is not None and (color is not None or bgcolor is not None):
                self.colors[scope] = {
                    "name": name,
                    "color": self.strip_color(color),
                    "bgcolor": self.strip_color(bgcolor),
                    "style": style
                }

    def strip_color(self, color, simple_strip=False):
        if color is None or color.strip() == "":
            return None
        elif not self.strip_trans:
            return color.replace(" ", "")
        rgba = RGBA(color.replace(" ", ""))
        if not simple_strip:
            rgba.apply_alpha(self.bground if self.bground != "" else "#FFFFFF")
        if self.track_dark_background:
            lumens = rgba.luminance()
            if self.dark_lumens is None or lumens < self.dark_lumens:
                self.dark_lumens = lumens
        return rgba.get_rgb()

    def get_general_colors(self):
        return self.bground, self.fground, self.sbground, self.sfground, self.gbground, self.gfground

    def get_darkest_lumen(self):
        return self.dark_lumens

    def get_plist_file(self):
        return self.plist_file

    def get_scheme_file(self):
        return self.scheme_file

    def guess_color(self, view, pt, scope_key):
        color = self.fground
        bgcolor = self.bground
        style = set([])
        color_selector = "foreground"
        style_selectors = {"bold": "", "italic": ""}
        bg_selector = "background"
        if scope_key in self.matched:
            color = self.matched[scope_key]["color"]
            style = self.matched[scope_key]["style"]
            bgcolor = self.matched[scope_key]["bgcolor"]
            selectors = self.matched[scope_key]["selectors"]
            color_selector, bg_selector, style_selectors = selectors["color"], selectors["background"], selectors["style"]
        else:
            best_match_bg = 0
            best_match_fg = 0
            best_match_style = 0
            for key in self.colors:
                match = view.score_selector(pt, key)
                if self.colors[key]["color"] is not None and match > best_match_fg:
                    best_match_fg = match
                    color = self.colors[key]["color"]
                    color_selector = self.colors[key]["name"]
                if self.colors[key]["style"] is not None and match > best_match_style:
                    best_match_style = match
                    for s in self.colors[key]["style"]:
                        style.add(s)
                        if s == "bold":
                            style_selectors["bold"] = self.colors[key]["name"]
                        elif s == "italic":
                            style_selectors["italic"] = self.colors[key]["name"]
                if self.colors[key]["bgcolor"] is not None and match > best_match_bg:
                    best_match_bg = match
                    bgcolor = self.colors[key]["bgcolor"]
                    bg_selector = self.colors[key]["name"]
            self.matched[scope_key] = {
                "color": color, "bgcolor": bgcolor,
                "style": style, "selectors": {
                    "color": color_selector,
                    "background": bg_selector,
                    "style": style_selectors
                }
            }
        if len(style) == 0:
            style = "normal"
        else:
            style = ' '.join(style)
        return color, style, bgcolor, color_selector, bg_selector, style_selectors

    def shift_background_brightness(self, lumens_limit):
        dlumen = self.get_darkest_lumen()
        if dlumen is not None and dlumen < lumens_limit:
            factor = 1 + ((lumens_limit - dlumen) / 255.0)
            for k, v in self.colors.items():
                fg, bg = v["color"], v["bgcolor"]
                if fg is not None:
                    self.colors[k]["color"] = self.apply_brightness(fg, factor)
                if bg is not None:
                    self.colors[k]["bgcolor"] = self.apply_brightness(bg, factor)
            self.bground = self.apply_brightness(self.bground, factor)
            self.fground = self.apply_brightness(self.fground, factor)
            self.sbground = self.apply_brightness(self.sbground, factor)
            if self.sfground is not None:
                self.sfground = self.apply_brightness(self.sfground, factor)
            self.gbground = self.apply_brightness(self.gbground, factor)
            self.gfground = self.apply_brightness(self.gfground, factor)

    def apply_brightness(self, color, shift_factor):
        rgba = RGBA(color)
        if shift_factor is not None:
            rgba.brightness(shift_factor)
        return rgba.get_rgb()
<|endoftext|>