diff --git a/QT/Input_Dialog.py b/QT/Input_Dialog.py new file mode 100644 index 0000000..245212e --- /dev/null +++ b/QT/Input_Dialog.py @@ -0,0 +1,164 @@ +import collections +from PySide6 import QtCore, QtWidgets +from QT.Main_Window import Attr_Dict + +class Input_Dialog (QtWidgets.QDialog) : + + def __init__ (self, title, ** spec) : + super ().__init__ (spec.pop ("parent", None)) + self.setWindowTitle (title) + self.layout = L = QtWidgets.QFormLayout (self) + self.widgets = {} + self.min_width = spec.pop ("_MIN_WIDTH", 200) + self.field_spec = spec + self.dependencies = collections.defaultdict (list) + for name, fspec in spec.items () : + type = fspec.get ("type") + default = fspec.get ("default") + if type is None and default is not None : + type = default.__class__.__name__ + getattr (self, "_add_%s" % type, self._add_str) \ + (L, name, fspec, default, type) + for attr, format in fspec.get ("auto", {}).items () : + self.dependencies [attr].append ((name, format)) + layout = QtWidgets.QHBoxLayout () + ok = QtWidgets.QPushButton ("OK") + ok.clicked.connect (self.store_values) + cancel = QtWidgets.QPushButton ("Cancel") + cancel.clicked.connect (self.reject) + layout.addStretch (10) + layout.addWidget (ok) + layout.addWidget (cancel) + ok.setDefault (True) + L.addRow (layout) + self.setMinimumWidth (self.min_width) + self.result = Attr_Dict () + # end def __init__ + + def get_values (self) : + data = Attr_Dict () + errors = {} + for name, (getter, _) in self.widgets.items () : + data [name] = value = getter () + fspec = self.field_spec [name] + if not value and fspec.get ("required", False) : + errors [name] = "Required" + return data, errors + # end def store_values + + def store_values (self) : + self.result, errors = self.get_values () + if not errors : + self.accept () + # end def store_values + + def _auto_update (self, changed) : + if changed in self.dependencies : + values, _ = self.get_values () + for attr, format in self.dependencies [changed] : + if not values.get (attr) : + self.widgets [attr] [1] (format % values) + # end def _auto_update + + def _add_str (self, L, name, fspec, default, type) : + widget = QtWidgets.QLineEdit () + widget.setText (default or "") + self.widgets [name] = widget.text, widget.setText + L.addRow (fspec.get ("title", name.capitalize ()), widget) + widget.editingFinished.connect \ + (lambda : self._auto_update (name)) + # end def _add_str + + def _add_int (self, L, name, fspec, default, type) : + widget = QtWidgets.QSpinBox () + widget.setRange (-65536, 0x7FFFFFFF) + widget.setValue (default or 0) + self.widgets [name] = widget.value, widget.setValue + L.addRow (fspec.get ("title", name.capitalize ()), widget) + widget.editingFinished.connect \ + (lambda : self._auto_update (name)) + # end def _add_int + + def _add_file_dialog (self, cb, L, name, fspec, default) : + layout = QtWidgets.QHBoxLayout () + current = QtWidgets.QLineEdit () + current.setText (default or "") + current.setEnabled (False) + change = QtWidgets.QPushButton ("...") + change.setFixedWidth (24) + change.clicked.connect (lambda : cb (fspec, current)) + layout.addWidget (current) + layout.addWidget (change) + layout.setStretch (0, 10) + self.widgets [name] = current.text, current.setText + L.addRow (fspec.get ("title", name.capitalize ()), layout) + self.min_width = max (self.min_width, 600) + # end def _add_file_dialog + + def _add_open_filename (self, L, name, fspec, default, type) : + self._add_file_dialog \ + (self._choose_file_dialog, L, name, fspec, default) + # end def _add_open_filename + + def _choose_file_dialog (self, fspec, current) : + filter = fspec.get ("filter", ()) + filter = ";;".join ("%s (%s)" % (t, e) for (t, e) in filter) + file_name = QtWidgets.QFileDialog.getOpenFileName \ + ( filter = filter + ) [0] + if file_name : + current.setText (file_name) + # end def _choose_file_dialog + + def _add_save_filename (self, L, name, fspec, default, type) : + self._add_file_dialog \ + (self._save_file_dialog, L, name, fspec, default) + # end def _add_open_filename + + def _save_file_dialog (self, fspec, current) : + filter = fspec.get ("filter", ()) + filter = ";;".join ("%s (%s)" % (t, e) for (t, e) in filter) + file_name = QtWidgets.QFileDialog.getSaveFileName \ + ( filter = filter + ) [0] + if file_name : + current.setText (file_name) + # end def _save_file_dialog + + def _add_drop_down (self, L, name, fspec, default, type) : + widget = QtWidgets.QComboBox () + for idx, e in enumerate \ + (sorted (fspec ["choices"], key = lambda e : e ["label"])) : + widget.addItem (e ["label"], e ["value"]) + if "tooltip" in e : + widget.setItemData \ + (idx, e ["tooltip"], QtCore.Qt.ToolTipRole) + self.widgets [name] = widget.currentText, widget.setCurrentText + L.addRow (fspec.get ("title", name.capitalize ()), widget) + widget.currentTextChanged.connect \ + (lambda : self._auto_update (name)) + # end def _add_drop_down + + def run (self) : + if self.exec () == 1 : + return self.result + # end def run + +# end class Input_Dialog + +if __name__ == "__main__" : + app = QtWidgets.QApplication ([]) + r = Input_Dialog \ + ( "Test" + , name = {"default": "", "title" : "Layer Name", "required": True} + , file_name = {"default": "", "title" : "Filename" + , "type" : "save_filename" + , "filter": ( ( "Layer Files", "*.layer") + , ( "Json Files", "*.json") + , ( "All Files", "*.*") + ) + , "auto" : {"name" : "%(name)s/%(name)s.layer"} + } + ).run () + if r is not None : + print (r) \ No newline at end of file diff --git a/QT/Main_Window.py b/QT/Main_Window.py new file mode 100644 index 0000000..ff595e6 --- /dev/null +++ b/QT/Main_Window.py @@ -0,0 +1,114 @@ +from PySide6 import QtWidgets, QtCore, QtGui +import os + +class Attr_Dict (dict) : + + def __getattr__ (self, key) : + return self [key] + # end def __getattr__ + +# end class Attr_Dict + +class Main_Window (QtWidgets.QMainWindow) : + + DEFAULT_SETTINGS_FILE = "settings.ini" + + APP_NAME = "Main Window" + VERSION = "0" + APP_ORGANISATION = ("Org-Name", "Org-Domain") + + def __init__ (self, app, settings) : + super ().__init__ () + self.settings = settings + self.setWindowTitle ("%s %s" % (self.APP_NAME, self.VERSION)) + self._setupActions (app) + self._setupGui () + if not self.restore_settings (settings) : + self.show () + # end def __init__ + + def _setupActions (self, app) : + self.actions = Attr_Dict () + self._add_action ("exit", "Exit", self.close, icon = ":/icons/exit.png") + # end def _setupActions + + def _add_action (self, name, label, callback, icon = None, ** options) : + A = QtGui.QAction (label, self, ** options) + if icon : + A.setIcon (QtGui.QIcon (icon)) + self.actions [name] = A + A.triggered.connect (callback) + # end def _add_action + + def _setupGui (self) : + pass + # end def _setupGui + + def closeEvent (self, * args) : + self.save_settings () + super ().closeEvent (* args) + # end def closeEvent + + @classmethod + def _open_settings (cls, settings_file = None) : + if not settings_file : + settings_file = os.path.join (cls.APP_DIR, cls.DEFAULT_SETTINGS_FILE) + QtCore.QCoreApplication.setOrganizationName (cls.APP_ORGANISATION [0]) + QtCore.QCoreApplication.setOrganizationDomain (cls.APP_ORGANISATION [1]) + QtCore.QCoreApplication.setApplicationName (cls.APP_NAME) + return QtCore.QSettings (settings_file, QtCore.QSettings.IniFormat) + # end def _open_settings + + @classmethod + def load_settings (cls, cmd) : + settings = cls._open_settings (cmd.settings_file) + ps = cls._load_app_settings (settings) + settings.beginGroup ("TempSettings") + settings.setValue ("settingsFile", cmd.settings_file) + for n, v in ps.items () : + if getattr (cmd, n, None) != None : + v = getattr (cmd, n) + settings.setValue (n, v) + settings.endGroup () + return settings + # end def load_settings + + def restore_settings (self, settings) : + self.settings = settings + QtCore.QTimer.singleShot (1, self._restore_settings) + # end def restore_settings + + def _restore_settings (self) : + settings = self.settings + settings.beginGroup ("MainWindow") + self.restoreGeometry (settings.value ("geometry" )) + self.restoreState (settings.value ("windowState")) + settings.endGroup () + # end def _restore_settings + + def save_settings (self) : + settings = self.settings + settings.beginGroup ("MainWindow") + settings.setValue ("geometry", self.saveGeometry ()) + settings.setValue ("windowState", self.saveState ()) + settings.endGroup () + # end def save_settings + + @classmethod + def run (cls, settings) : + QtWidgets.QApplication.setStyle ("macintosh") + app = QtWidgets.QApplication ([]) + if cls.pre_main_window_action (settings) : + self = cls (app, settings) + if self.isVisible () : + app.exec_ () + # end def run + + @classmethod + def pre_main_window_action (cls, settings) : + return True + # end def pre_main_window_action + +# end class Main_Window + +### __END__ Main_Window diff --git a/QT/Model_Adapter.py b/QT/Model_Adapter.py new file mode 100644 index 0000000..655eed0 --- /dev/null +++ b/QT/Model_Adapter.py @@ -0,0 +1,448 @@ +# Simplified tree model (based on a QtAbstractItemModel) + +from PySide2 import QtCore, QtWidgets +from Accessor import Getter +import contextlib + +Qt = QtCore.Qt + +class Column : + """Column definition""" + + Flag_Map = dict \ + ( no_flag = (Qt.NoItemFlags, False) + , selectable = (Qt.ItemIsSelectable, True) + , enabled = (Qt.ItemIsEnabled, True) + , editable = (Qt.ItemIsEditable, False) + , drag = (Qt.ItemIsDragEnabled, False) + , drop = (Qt.ItemIsDropEnabled, False) + , checkable = (Qt.ItemIsUserCheckable, False) + , tristate = (Qt.ItemIsTristate, False) + ) + + Clone_Attributes = ("name", "attr", "getter", "_flags", "model") + + def __init__ ( self, name, attr = None, getter = None, model = None + , ** flags + ) : + self.name = name + self.attr = attr or name.lower () + self.getter = getter or getattr (Getter, self.attr) + self.model = model + self._flags = flags + # end def __init__ + + def background (self, row) : + return None + # end def background + + def __call__ (self, row) : + return self.getter (row) + # end def __call__ + + def clone (self, ** overrides) : + d = dict \ + ( (a, overrides.get (a, getattr (self, a))) + for a in self.Clone_Attributes + ) + d.update (d.pop ("_flags", {})) + return self.__class__ (** d) + # end def clone + + def checkstate (self, row) : + return None + # end def checkstate + + def decoration (self, row) : + return None + # end def decoration + + def flags (self, row) : + result = 0 + for attr, (flag, default) in self.Flag_Map.items () : + v = self._flags.get (attr, default) + if callable (v) : + v = v (row) + if v : + result |= flag + return result + # end def flags + + def font (self, row) : + return None + # end def font + + def foreground (self, row) : + return None + # end def foreground + + def text_aligment (self, row) : + return None + # end def text_aligment + +# end class Column + +class _Root_Item_ : + """Simple models which can be used as hidden root element of no real root + element exists in the model + """ + parent = None + + def __init__ (self, * children) : + self.children = [] + for c in children : + self.append (c) + # end def __init__ + + def append (self, child) : + self.children.append (child) + child.parent = self + # end def append + + def index (self, child) : + return self.children.index (child) + # end def index + + def __delitem__ (self, key) : + del self.children [key] + # end def __delitem__ + + def __getitem__ (self, key) : + return self.children [key] + # end def __getitem__ + + def __len__ (self) : + return len (self.children) + # end def __len__ + + def __setitem__ (self, key, value) : + self.children [key] = value + if not isinstance (value, (list, tuple)) : + value = (value, ) + for v in value : + v.parent = self + # end def __setitem__ + + def __str__ (self) : + return "" + # end def __str__ + +# end class _Root_Item_ + +class Model_Adapter (QtCore.QAbstractItemModel) : + """The underlying QT class handling the interface to the wrapper class + """ + + ### QT Interface + def columnCount (self, parent) : + return len (self.Columns) + # end def columnCount + + def data (self, index, role) : + #print ("data", role) + if not index.isValid () : + return None + column = index.column () + item = index.internalPointer () + col_desc = self.Columns [column] + if role == Qt.DisplayRole or role == Qt.EditRole : + return col_desc (item) + elif role == Qt.CheckStateRole : + return col_desc.checkstate (item) + elif role == Qt.BackgroundRole : + return col_desc.background (item) + elif role == Qt.ForegroundRole : + return col_desc.foreground (item) + elif role == Qt.FontRole : + return col_desc.font (item) + elif role == Qt.DecorationRole : + return col_desc.decoration (item) + elif role == Qt.TextAlignmentRole : + return col_desc.text_aligment (item) + return None + # end def data + + def flags (self, index) : + if index.isValid () : + data = index.internalPointer () + col = index.column () + return self.Columns [col].flags (data) + return super ().flags (index) + # end def flags + + def index (self, row, column, parent_index, parent = None) : + #debug = "ID: %d <%-7s>" % \ + # (row, self.item_path (parent_index, ", ") or "R") + if parent is None : + if not parent_index.isValid () : + parent = self.root_item + else : + parent = parent_index.internalPointer () + try : + child = parent [row] + #print ("%s, P<%s>, C<%s>" %(1, parent, child)) + return self.createIndex (row, column, child) + except IndexError as exc: + #print (debug, exc) + return self.invalid_index + # end def index + + def index_for_item (self, item, column = 0) : + if item.parent : + row = item.parent.index (item) + return self.createIndex (row, column, item) + return self.invalid_index + # end def index_for_item + + def item_from_index (self, index) : + if index.isValid () : + return index.internalPointer () + # end def item_from_index + + @property + def invalid_index (self) : + return QtCore.QModelIndex () + # end def invalid_index + + def parent (self, index) : + if not index.isValid () : + return self.invalid_index + item = index.internalPointer () + parent = item.parent + if parent is self.root_item : + return self.invalid_index + if parent.parent : + row = parent.parent.index (parent) + else : + row = 0 + return self.createIndex (row, 0, parent) + # end def parent + + def rowCount (self, parent_index) : + if not parent_index.isValid () : + parent = self.root_item + else: + parent = parent_index.internalPointer () + #path = self.item_path (parent_index, ", ") or "R" + #print ("RC: %-7s [%d]" % (path, len (parent))) + return len (parent) + # end def rowCount + + def setData (self, index, value, role) : + if index.isValid () : + if self.set_data (index, value, role) : + self.dataChanged.emit (index, index) + return True + return False + # end def setData + + ### QT Optional + def headerData (self, section, orientation, role) : + if ( (orientation == Qt.Horizontal) + and (role == Qt.DisplayRole) + ) : + return self.Columns [section].name + return None + # end def headerData + + def hasChildren (self, index) : + result = True + if index.isValid () : + result = bool (len (index.internalPointer ())) + #path = self.item_path (parent_index, ", ") or "R" + #print ("HC: %-7s [%s]" % (path, result)) + return result + # end def hasChildren + + ### mime data handling + def supportedDropActions (self) : + default = super ().supportedDropActions () + return getattr (self, "supported_drop_actions", default) + # end def supportedDropActions + + def mimeTypes (self) : + model_fct = getattr (self, "mime_types", None) + result = super ().mimeTypes () + if model_fct is not None : + result = model_fct (result) + return result + # end def mimeTypes + + def mimeData (self, indexes) : + model_fct = getattr (self, "mime_data", None) + if model_fct : + items = [i.internalPointer () for i in indexes] + result = QtCore.QMimeData () + model_fct (result, items, indexes) + return result + return super ().mimeData (indexes) + # end def mimeData + + def dropMimeData (self, mime_data, action, row, column, parent) : + model_fct = getattr (self, "drop_mime_data", None) + if model_fct is None : + return super ().dropMimeData \ + (mime_data, action, row, column, parent) + return model_fct \ + (mime_data, action, row, column, parent) + # end def dropMimeData + + ### Python Interface + Columns = () + Supported_Drop_Actions = () + mimeTypes = () + + def __init__ (self, * items, ** kw) : + super ().__init__ () + self.Columns = [c.clone (model = self) for c in self.Columns] + root_item = kw.pop ("root", None) + if root_item is None : + root_item = _Root_Item_ (* items) + self.root_item = root_item + # end def __init__ + + def append (self, item, parent = None) : + if parent is None : + parent = self.root_item + start = len (parent) + index = self.index_for_item (parent) + self.beginInsertRows (index, start, start) + parent.append (item) + self.endInsertRows () + # end def append + + def dump (self, root_index = None, indend = "") : + if root_index is None : + root_index = self.invalid_index + result = [] + for i in range (self.rowCount (root_index)) : + index = self.index (i, 0, root_index) + item = self.item_from_index (index) + result.append ("%s%s" % (indend, item)) + result.extend (self.dump (index, indend + ".")) + if not indend : + print ("---- Dump by index functions") + print ("\n".join (result)) + print ("---- Dump end") + return result + # end def dump + + def _walk (self, path, root, root_index = None) : + for n in range (len (root)) : + c = root [n] + p = path + (n, ) + if root_index is not None : + index = self.index (n, 0, root_index) + yield p, c, index + yield from self._walk (p, c, index) + else : + yield p, c + yield from self._walk (p, c) + # end def _walk + + def walk (self, with_index = False) : + index = None + if with_index : + index = self.invalid_index + return self._walk ((), self.root_item, index) + # end def walk + + def item_path (self, index, joiner = None) : + result = [] + if index.isValid () : + result.extend (self.item_path (self.parent (index))) + result.append (index.row ()) + if joiner : + result = joiner.join (result) + return result + # end def item_path + + def qindex (self, item, col = 0) : + if item is self.root_item : + row = 0 + else : + row = item.parent.index (item) + return self.createIndex (row, col, item.parent) + # end def qindex + + def insert_row (self, item, parent_index = None, start = -1) : + count = 1 + parent_index = parent_index or self.invalid_index + if start == -1 : + start = self.rowCount (parent_index) + if parent_index.isValid () : + parent = parent_index.internalPointer () + else : + parent = self.root_item + self.beginInsertRows (parent_index, start, start + count - 1) + parent [start:start] = [item] + self.endInsertRows () + return self.index (start, 0, parent_index) + # end def insert_row + + def item_from_index (self, index) : + if index.isValid () : + return index.internalPointer () + # end def item_from_index + + def remove (self, index = None, item = None) : + if index is None : + parent = item.parent + row = parent.index (item) + parent_index = self.qindex (parent) + else : + row = index.row () + parent_index = index.parent () + if parent_index.isValid () : + parent = parent_index.internalPointer () + else : + parent = self.root_item + item = parent [row] + self.beginRemoveRows (parent_index, row, row + 1) + del parent [row] + if hasattr (item, "delete") : + item.delete () + self.endRemoveRows () + # end def remove + + @contextlib.contextmanager + def replace_model (self) : + self.beginResetModel () + yield + self.endResetModel () + # end def replace_model + + def set_data (self, index, value, role) : + return False + # end def set_data + +# end class Model_Adapter + +class Tree_View (QtWidgets.QTreeView) : + + def expanded_nodes (self) : + expanded_path = [] + model = self.model () + if model : + for p, _, index in model.walk (True) : + if self.isExpanded (index) : + expanded_path.append (p) + return expanded_path + # end def expanded_nodes + + def restore_expanded_nodes (self, expanded) : + model = self.model () + if model : + indixes = {} + iv_index = model.invalid_index + for path in expanded : + parent_index = indixes.get (path [:-1], iv_index) + index = model.index (path [-1], 0, parent_index) + indixes [path] = index + item = model.item_from_index (index) + if item and len (item) : + self.setExpanded (index, True) + # end def restore_expanded_nodes + +# end class Tree_View +### __END__ QT.Model_Adapter diff --git a/QT/Push_Button.py b/QT/Push_Button.py new file mode 100644 index 0000000..39dc3e1 --- /dev/null +++ b/QT/Push_Button.py @@ -0,0 +1,24 @@ +from PySide2 import QtWidgets, QtGui + + +def Push_Button (text, clicked = None, icon = None) : + button = QtWidgets.QPushButton (text) + if clicked : + button.clicked.connect (clicked) + if icon : + button.setIcon (QtGui.QIcon (icon)) + return button +# end def Push_Button + +#class Push_Button (QtWidgets.QPushButton) : +# +# def __init__ ( self, text +# , callback = None +# , icon = None +# ) : +# super ().__init__ (text) +# if callback : +# self.clicked.connect (callback) +# if icon : +# self.setIcon (QtGui.QIcon (icon)) +# # end def \ No newline at end of file diff --git a/QT/Screen_Designer.ini b/QT/Screen_Designer.ini new file mode 100644 index 0000000..b6cc04f --- /dev/null +++ b/QT/Screen_Designer.ini @@ -0,0 +1,2 @@ +[TempSettings] +settingsFile=@Invalid() diff --git a/QT/__init__.py b/QT/__init__.py new file mode 100644 index 0000000..660476a --- /dev/null +++ b/QT/__init__.py @@ -0,0 +1,3 @@ +### package for commone QT classes +from PySide6 import QtWidgets, QtCore, QtGui, QtCore +QT = QtCore.Qt \ No newline at end of file diff --git a/QT/__pycache__/Input_Dialog.cpython-312.pyc b/QT/__pycache__/Input_Dialog.cpython-312.pyc new file mode 100644 index 0000000..a1fd07e Binary files /dev/null and b/QT/__pycache__/Input_Dialog.cpython-312.pyc differ diff --git a/QT/__pycache__/Main_Window.cpython-312.pyc b/QT/__pycache__/Main_Window.cpython-312.pyc new file mode 100644 index 0000000..874bd5a Binary files /dev/null and b/QT/__pycache__/Main_Window.cpython-312.pyc differ diff --git a/QT/__pycache__/__init__.cpython-312.pyc b/QT/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..beacecf Binary files /dev/null and b/QT/__pycache__/__init__.cpython-312.pyc differ