How to create a settings manager (Phoenix)

Keywords : Setting, Manager.


Demonstrating :

Tested py3.x, wx4.x and Win10.

Are you ready to use some samples ? ;)

Test, modify, correct, complete, improve and share your discoveries ! (!)


First example

img_sample_one.png

   1 # sample_one.py
   2 
   3 import os
   4 import zlib
   5 from   xml.etree.ElementTree import ElementTree, Element, SubElement
   6 import xml.etree.ElementTree as ETree
   7 import wx
   8 
   9 # def startupStringToPos
  10 # def startupPosToString
  11 # class MySettingsManager
  12 # class MyDlgSettings
  13 # class MyFrame
  14 # class MyApp
  15 
  16 #---------------------------------------------------------------------------
  17 
  18 def startupStringToPos(value):
  19     if value == 'CenterScreen':
  20         return wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL
  21     if value == 'LastKnownPos':
  22         return wx.ALIGN_NOT
  23     iHPos, iVPos = 0, 0
  24     if 'Left' in value:
  25         iHPos = wx.LEFT
  26     elif 'Right' in value:
  27         iHPos = wx.RIGHT
  28     else:
  29         iHPos = wx.ALIGN_CENTER_HORIZONTAL
  30     if 'Top' in value:
  31         iVPos = wx.TOP
  32     elif 'Bottom' in value:
  33         iVPos = wx.BOTTOM
  34     else:
  35         iVPos = wx.ALIGN_CENTER_VERTICAL
  36 
  37     return iHPos|iVPos
  38 
  39 #---------------------------------------------------------------------------
  40 
  41 def startupPosToString(value):
  42     if value == (wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL):
  43         return 'CenterScreen'
  44     if value == wx.ALIGN_NOT:
  45         return 'LastKnownPos'
  46     sHPos, sVPos = '', ''
  47     if (value&wx.LEFT) == wx.LEFT:
  48         sHPos = 'Left'
  49     elif (value&wx.RIGHT) == wx.RIGHT:
  50         sHPos = 'Right'
  51     else:
  52         sHPos = 'Center'
  53     if (value&wx.TOP) == wx.TOP:
  54         sVPos = 'Top'
  55     elif (value&wx.BOTTOM) == wx.BOTTOM:
  56         sVPos = 'Bottom'
  57     else:
  58         sVPos = 'Center'
  59     return sVPos + sHPos
  60 
  61 #---------------------------------------------------------------------------
  62 
  63 class MySettingsManager(object):
  64     _instance = None
  65     # Default settings
  66     _iStartPos = wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL
  67     _ptStartPos = wx.DefaultPosition
  68     _szStartSize = wx.Size(600, 400)
  69     _bSingleInstanceOnly = False
  70     _bCompSettings = False
  71     # Other default values
  72     _bModified = False
  73     _sSettingsFName = 'settings'
  74 
  75     def __new__(cls):
  76         if cls._instance is None:
  77             cls._instance = super(MySettingsManager, cls).__new__(cls)
  78             cls._instance.Initialize()
  79         return cls._instance
  80 
  81     def Initialize(self):
  82         self._bModified = False
  83         sScriptDir = os.path.dirname(os.path.abspath(__file__))
  84         if os.path.isfile(os.path.join(sScriptDir, self._sSettingsFName)):
  85             self._sSettingsPath = sScriptDir
  86         else:
  87             self._sSettingsPath = wx.StandardPaths.Get().GetUserDataDir()
  88 
  89     def _getDatasPath(self):
  90         return self._sSettingsPath
  91 
  92     def _isModified(self):
  93         return self._bModified
  94 
  95     def SetModified(self):
  96         self._bModified = True
  97 
  98     def _getMainWndStartupPos(self):
  99         if self._iStartPos == wx.ALIGN_NOT:
 100             return self._iStartPos, self._ptStartPos
 101 
 102         return self._iStartPos, None
 103 
 104     def _setMainWndStartupPos(self, mode, pos=None):
 105         if mode != self._iStartPos:
 106             self._iStartPos = mode
 107             self._bModified = True
 108         if not pos is None:
 109             if pos != self._ptStartPos:
 110                 self._ptStartPos = pos
 111                 self._bModified = True
 112 
 113     def _getMainWndStartupSize(self):
 114         return self._szStartSize
 115 
 116     def _setMainWndStartupSize(self, size):
 117         if size != self._szStartSize:
 118             self._szStartSize = size
 119             self._bModified = True
 120 
 121     def _getMainWndStartupRect(self):
 122         return wx.Rect(self._ptStartPos, self._szStartSize)
 123 
 124     def _setMainWndStartupRect(self, rect):
 125         if not rect == wx.Rect(self._ptStartPos, self._szStartSize):
 126             self._ptStartPos = rect.GetPosition()
 127             self._szStartSize = rect.GetSize()
 128             self._bModified = True
 129 
 130     def _getMultipleInstancesAllowed(self):
 131         return self._bSingleInstanceOnly is False
 132 
 133     def _setMultipleInstancesAllowed(self, value):
 134         if self._bSingleInstanceOnly == value:
 135             self._bSingleInstanceOnly = not value
 136             self._bModified = True
 137 
 138     def _getCompressSettings(self):
 139         return self._bCompSettings
 140 
 141     def _setCompressSettings(self, value):
 142         if not self._bCompSettings == value:
 143             self._bCompSettings = value
 144             self._bModified = True
 145 
 146     def ReadSettings(self):
 147         # Check whether the settings folder exists
 148         if not os.path.isdir(self._sSettingsPath):
 149             return
 150         # Construct the settings file name and check if it exists
 151         sFName = os.path.join(self._sSettingsPath, self._sSettingsFName)
 152         if not os.path.isfile(sFName):
 153             return
 154         # Try to load the xml settings file
 155         f = open(sFName, 'rb')
 156         datas = f.read()
 157         f.close()
 158         if datas[0:5] != b'<?xml':
 159             # Compressed file
 160             datas = zlib.decompress(datas)
 161 
 162         tree = ElementTree(ETree.fromstring(datas.decode()))
 163         #tree.parse(sFName)
 164         rootNode = tree.getroot()
 165         #Read each xml entry
 166         for childNode in rootNode:
 167             nodeName = childNode.tag
 168             if nodeName == 'StartupPos':
 169                 self._iStartPos = startupStringToPos(childNode.get('Value', 'CenterScreen'))
 170                 iX = int(childNode.get('X', '-1'))
 171                 iY = int(childNode.get('Y', '-1'))
 172                 self._ptStartPos = wx.Point(iX, iY)
 173                 iW = int(childNode.get('W', '-1'))
 174                 iH = int(childNode.get('H', '-1'))
 175                 self._szStartSize = wx.Size(iW, iH)
 176             elif nodeName == 'MultiInstances':
 177                 self._bSingleInstanceOnly = (childNode.text != 'Allowed')
 178             elif nodeName == 'CompressSettingsFile':
 179                 self._bCompSettings = (childNode.text == 'Yes')
 180 
 181     def SaveSettings(self):
 182         # Create the root node of the xml tree
 183         rootNode = Element('Settings-file')
 184         rootNode.set('Version', '1.0')
 185 
 186         # Position of the main window at application startup
 187         node = SubElement(rootNode, 'StartupPos')
 188         node.set('Value', startupPosToString(self._iStartPos))
 189         iX, iY = self._ptStartPos.Get()
 190         iW, iH = self._szStartSize.Get()
 191         node.set('X', str(iX))
 192         node.set('Y', str(iY))
 193         node.set('W', str(iW))
 194         node.set('H', str(iH))
 195 
 196         # Allowing or not multiple instances of the application
 197         node = SubElement(rootNode, 'MultiInstances')
 198         if self._bSingleInstanceOnly:
 199             node.text = 'Not-Allowed'
 200         else:
 201             node.text = 'Allowed'
 202 
 203         # Settings file compression
 204         node = SubElement(rootNode, 'CompressSettingsFile')
 205         if self._bCompSettings:
 206             node.text = 'Yes'
 207         else:
 208             node.text = 'No'
 209 
 210         # Check if the settings folder exists
 211         if not os.path.isdir(self._sSettingsPath):
 212             os.makedirs(self._sSettingsPath)
 213         # Construct the settings file name and check if it exists
 214         sFName = os.path.join(self._sSettingsPath, self._sSettingsFName)
 215         if os.path.isfile(sFName):
 216             os.remove(sFName)
 217         # Save the new xml file
 218         if self._bCompSettings:
 219             datas = ETree.tostring(rootNode, encoding='UTF8', method='xml')
 220             datas = datas.replace(b'UTF8', b'UTF-8')
 221             compDatas = zlib.compress(datas, 9)
 222             f = open(sFName, 'wb')
 223             f.write(compDatas)
 224             f.close()
 225         else:
 226             tree = ElementTree(rootNode)
 227             tree.write(sFName, encoding='UTF-8', xml_declaration=True)
 228 
 229     DatasPath = property(_getDatasPath)
 230     Modified = property(_isModified)
 231     MainWndStartupPos = property(_getMainWndStartupPos, _setMainWndStartupPos)
 232     MainWndStartupSize = property(_getMainWndStartupSize, _setMainWndStartupSize)
 233     MainWndStartupRect = property(_getMainWndStartupRect, _setMainWndStartupRect)
 234     MultipleInstancesAllowed = property(_getMultipleInstancesAllowed, _setMultipleInstancesAllowed)
 235     CompressSettingsFile = property(_getCompressSettings, _setCompressSettings)
 236 
 237 #---------------------------------------------------------------------------
 238 
 239 class MyDlgSettings(wx.Dialog):
 240     def __init__(self, parent):
 241         wx.Dialog.__init__(self, parent, title='Settings')
 242 
 243         self._createInterface()
 244         self._fillControls()
 245         self._connectEvents()
 246         self.CenterOnParent()
 247 
 248     def _createInterface(self):
 249         szrMain = wx.BoxSizer(wx.VERTICAL)
 250 
 251         nBook = wx.Notebook(self, -1)
 252 
 253         # Tab "General"
 254         page = wx.Panel(nBook, wx.ID_STATIC)
 255         pageszr = wx.BoxSizer(wx.VERTICAL)
 256 
 257         box = wx.StaticBoxSizer(wx.VERTICAL, page, 'Position of the main window:')
 258         label = wx.StaticText(page, wx.ID_STATIC, 'Position of the main window at application startup')
 259         box.Add(label, 0, wx.ALL, 5)
 260         lnszr = wx.BoxSizer(wx.HORIZONTAL)
 261         self.optStartType = []
 262         self.optStartType.append(wx.RadioButton(page, -1, 'Predefined position', style=wx.RB_GROUP))
 263         lnszr.Add(self.optStartType[0], 0, wx.ALL, 5)
 264         self.optStartType.append(wx.RadioButton(page, -1, 'Last registered position'))
 265         lnszr.Add(self.optStartType[1], 0, wx.TOP|wx.BOTTOM|wx.RIGHT, 5)
 266         box.Add(lnszr, 0, wx.ALL, 0)
 267 
 268         box2 = wx.StaticBoxSizer(wx.VERTICAL, page, 'Predefined positions:')
 269         self.stbPos = box2.GetStaticBox()
 270         flxszr = wx.FlexGridSizer(3, 5, 5)
 271         self.optDefPos = []
 272         index = -1
 273         for item in ['Top-Left', 'Top-Center', 'Top-Right', 'Middle-Left', 'Center-Screen', 'Middle-Right', 'Bottom-Left', 'Bottom-Center', 'Bottom-Right']:
 274             index += 1
 275             if index == 0:
 276                 self.optDefPos.append(wx.RadioButton(page, -1, item, style=wx.RB_GROUP))
 277             else:
 278                 self.optDefPos.append(wx.RadioButton(page, -1, item))
 279             flxszr.Add(self.optDefPos[index])
 280         flxszr.AddGrowableCol(0, 1)
 281         flxszr.AddGrowableCol(1, 1)
 282         box2.Add(flxszr, 0, wx.ALL|wx.EXPAND, 5)
 283         box.Add(box2, 0, wx.ALL|wx.EXPAND, 5)
 284 
 285         pageszr.Add(box, 0, wx.LEFT|wx.RIGHT|wx.EXPAND, 5)
 286 
 287         box = wx.StaticBoxSizer(wx.VERTICAL, page, 'Misc:')
 288         self.chkSingleInstance = wx.CheckBox(page, -1, 'Allow only one instance of the application')
 289         box.Add(self.chkSingleInstance, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM, 5)
 290         self.chkCompFile = wx.CheckBox(page, -1, 'Compress settings file (for size and privacy)')
 291         box.Add(self.chkCompFile, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM, 5)
 292         pageszr.Add(box, 0, wx.ALL|wx.EXPAND, 5)
 293 
 294         page.SetSizer(pageszr)
 295         nBook.AddPage(page, 'General')
 296 
 297         szrMain.Add(nBook, 1, wx.ALL|wx.EXPAND, 0)
 298 
 299         btnSizer = self.CreateSeparatedButtonSizer(wx.OK|wx.CANCEL|wx.APPLY)
 300 
 301         szrMain.Add(btnSizer, 0, wx.ALL|wx.EXPAND, 5)
 302 
 303         self.btnApply = self.FindWindowById(wx.ID_APPLY)
 304 
 305         self.SetSizer(szrMain)
 306         szrMain.SetSizeHints(self)
 307 
 308     def _connectEvents(self):
 309         self.Bind(wx.EVT_BUTTON, self.OnBtnOkClicked, id=wx.ID_OK)
 310         self.Bind(wx.EVT_BUTTON, self.OnBtnApplyClicked, id=wx.ID_APPLY)
 311         for item in self.optStartType:
 312             item.Bind(wx.EVT_RADIOBUTTON, self.OnStartupPosTypeChanged)
 313         self.Bind(wx.EVT_CHECKBOX, self.OnSomethingHasChanged)
 314         self.Bind(wx.EVT_RADIOBUTTON, self.OnSomethingHasChanged)
 315 
 316     def _fillControls(self):
 317         s = MySettingsManager()
 318         iStartPos, _ = s.MainWndStartupPos
 319         if iStartPos == wx.ALIGN_NOT:
 320             self.optStartType[1].SetValue(True)
 321             self.optDefPos[4].SetValue(True)
 322         else:
 323             self.optStartType[0].SetValue(True)
 324             if iStartPos == (wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL):
 325                 self.optDefPos[4].SetValue(True)
 326             else:
 327                 iX = 0
 328                 if (iStartPos&wx.RIGHT) == wx.RIGHT:
 329                     iX = 2
 330                 elif (iStartPos&wx.LEFT) == wx.LEFT:
 331                     iX = 0
 332                 else:
 333                     iX = 1
 334                 iY = 0
 335                 if (iStartPos&wx.BOTTOM) == wx.BOTTOM:
 336                     iY = 6
 337                 elif (iStartPos&wx.TOP) == wx.TOP:
 338                     iY = 0
 339                 else:
 340                     iY = 3
 341                 self.optDefPos[iX+iY].SetValue(True)
 342         self.OnStartupPosTypeChanged(None)
 343 
 344         self.chkSingleInstance.SetValue(s.MultipleInstancesAllowed is False)
 345         self.chkCompFile.SetValue(s.CompressSettingsFile)
 346 
 347         self.btnApply.Disable()
 348 
 349     def _applySettings(self):
 350         s = MySettingsManager()
 351         iIndex = wx.NOT_FOUND
 352         if self.optStartType[0].GetValue() is True:
 353             for i, o in enumerate(self.optDefPos):
 354                 if o.GetValue() is True:
 355                     iIndex = i
 356                     break
 357         iStartPos = wx.ALIGN_NOT
 358         if iIndex > wx.NOT_FOUND:
 359             iH = 0
 360             if iIndex in [0, 3, 6]:
 361                 iH = wx.LEFT
 362             elif iIndex in [1, 4, 7]:
 363                 iH = wx.CENTER
 364             else:
 365                 iH = wx.RIGHT
 366             iV = 0
 367             if (iIndex > -1) and (iIndex < 3):
 368                 iV = wx.TOP
 369             elif (iIndex > 2) and (iIndex < 6):
 370                 iV = wx.CENTER
 371             else:
 372                 iV = wx.BOTTOM
 373             if (iH == wx.CENTER) and (iV == wx.CENTER):
 374                 iStartPos = (wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
 375             else:
 376                 iStartPos = iH | iV
 377         s.MainWndStartupPos = iStartPos
 378 
 379         s.MultipleInstancesAllowed = self.chkSingleInstance.IsChecked() is False
 380         s.CompressSettingsFile = self.chkCompFile.IsChecked()
 381         self.btnApply.Disable()
 382 
 383     def OnBtnApplyClicked(self, evt):
 384         self._applySettings()
 385 
 386     def OnBtnOkClicked(self, evt):
 387         self._applySettings()
 388         self.EndModal(wx.ID_OK)
 389 
 390     def OnStartupPosTypeChanged(self, evt):
 391         bEnable = self.optStartType[0].GetValue()
 392         self.stbPos.Enable(bEnable)
 393         for item in self.optDefPos:
 394             item.Enable(bEnable)
 395         self.OnSomethingHasChanged(evt)
 396 
 397     def OnSomethingHasChanged(self, evt):
 398         self.btnApply.Enable()
 399 
 400 #---------------------------------------------------------------------------
 401 
 402 class MyFrame(wx.Frame):
 403     def __init__(self):
 404         wx.Frame.__init__(self, None, title=wx.GetApp().GetAppName())
 405         self.SetIcon(wx.Icon('wxwin.ico'))
 406 
 407         self._createControls()
 408 
 409         # Minimum client size of the main window
 410         self.SetMinClientSize(wx.Size(400, 300))
 411 
 412         s = MySettingsManager()
 413         mode, pos = s.MainWndStartupPos
 414         sz = s.MainWndStartupSize
 415         if mode == wx.ALIGN_NOT:
 416             if sz == wx.DefaultSize:
 417                 if pos == wx.DefaultPosition:
 418                     self.Maximize()
 419                 else:
 420                     self.CenterOnScreen()
 421             else:
 422                 self.Move(pos)
 423                 self.SetSize(sz)
 424         else:
 425             iWScr, iHScr = wx.GetDisplaySize()
 426             if sz == wx.DefaultSize:
 427                 sz = self.GetSize()
 428             pt = wx.DefaultPosition
 429             if (mode & wx.LEFT) == wx.LEFT:
 430                 pt.x = 0
 431             elif (mode & wx.RIGHT) == wx.RIGHT:
 432                 pt.x = iWScr - sz.GetWidth()
 433             else:
 434                 pt.x = (iWScr - sz.GetWidth()) // 2
 435             if (mode & wx.TOP) == wx.TOP:
 436                 pt.y = 0
 437             elif (mode & wx.BOTTOM) == wx.BOTTOM:
 438                 pt.y = iHScr - sz.GetHeight()
 439             else:
 440                 pt.y = (iHScr - sz.GetHeight()) // 2
 441             self.Move(pt)
 442             self.SetSize(sz)
 443 
 444         self._connectControls()
 445 
 446     def _createControls(self):
 447         # A Statusbar in the bottom of the window
 448         self.CreateStatusBar(1)
 449         sMsg = 'wxPython ' + wx.version()
 450         self.SetStatusText(sMsg)
 451 
 452         # Add a panel to the frame (needed under Windows to have a nice background)
 453         pnl = wx.Panel(self, wx.ID_ANY)
 454 
 455         szrMain = wx.BoxSizer(wx.VERTICAL)
 456         self._btnPrefs = wx.Button(pnl, wx.ID_ANY, 'Edit Settings')
 457         szrMain.Add(self._btnPrefs, 0, wx.ALL, 5)
 458         pnl.SetSizer(szrMain)
 459 
 460     def _connectControls(self):
 461         self.Bind(wx.EVT_SIZE, self._onWindowResized)
 462         self.Bind(wx.EVT_MOVE, self._onWindowMoved)
 463         self._btnPrefs.Bind(wx.EVT_BUTTON, self._onBtnEditPrefstClicked)
 464 
 465     def _onWindowResized(self, evt):
 466         if not self.IsShown():
 467             return
 468         s = MySettingsManager()
 469         if self.IsMaximized():
 470             s.MainWndStartupPos = wx.DefaultPosition
 471             s.MainWndStartupSize = wx.DefaultSize
 472         else:
 473             s.MainWndStartupRect = self.GetRect()
 474 
 475         evt.Skip()
 476 
 477     def _onWindowMoved(self, evt):
 478         if not self.IsShown():
 479             return
 480         MySettingsManager().MainWndStartupRect = self.GetRect()
 481 
 482         evt.Skip()
 483 
 484     def _onBtnEditPrefstClicked(self, evt):
 485         dlg = MyDlgSettings(self)
 486         dlg.ShowModal()
 487         dlg.Destroy()
 488 
 489 #---------------------------------------------------------------------------
 490 
 491 class MyApp(wx.App):
 492     _snglInstChecker = None
 493 
 494     def OnInit(self):
 495         print('Running wxPython ' + wx.version())
 496         # Set Current directory to the one containing this file
 497         os.chdir(os.path.dirname(os.path.abspath(__file__)))
 498 
 499         self.SetAppName('SettingsManager')
 500 
 501         s = MySettingsManager()
 502         s.ReadSettings()
 503         self._snglInstChecker = wx.SingleInstanceChecker()
 504         self._snglInstChecker.CreateDefault()
 505         if self._snglInstChecker.IsAnotherRunning() and not s.MultipleInstancesAllowed:
 506             wx.MessageBox('Another instance of this application is already running!', 'Multiple instances not allowed', wx.OK | wx.ICON_EXCLAMATION | wx.CENTER)
 507             return False
 508 
 509         # Create the main window
 510         frm = MyFrame()
 511         self.SetTopWindow(frm)
 512 
 513         frm.Show()
 514         return True
 515 
 516     def OnExit(self):
 517         s = MySettingsManager()
 518         if s.Modified is True:
 519             s.SaveSettings()
 520         return wx.App.OnExit(self)
 521 
 522 #---------------------------------------------------------------------------
 523 
 524 if __name__ == '__main__':
 525     app = MyApp()
 526     app.MainLoop()


Download source

source.zip


Additional Information

Link :

- - - - -

https://wiki.wxpython.org/TitleIndex

https://docs.wxpython.org/


Thanks to

Xaviou (sample_one.py coding), the wxPython community...


About this page

Date(d/m/y) Person (bot) Comments :

28/08/19 - Ecco (Created page for wxPython Phoenix).

14/01/23 - Ecco (Updated page and "source.zip" for Python 3.10.5).


Comments

- blah, blah, blah....

How to create a settings manager (Phoenix) (last edited 2023-01-14 10:51:33 by Ecco)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.