Source code for flexicon.code.System.ProjectSettingsOperations

#
#   ProjectSettingsOperations.py
#
#   Class: ProjectSettingsOperations
#          Project-level settings and configuration operations for FieldWorks
#          Language Explorer projects via SIL Language and Culture Model (LCM) API.
#
#   Platform: Python.NET
#             FieldWorks Version 9+
#
#   Copyright 2025
#

import logging

logger = logging.getLogger(__name__)

# Import FLEx LCM types
from SIL.LCModel import (
    ILangProject,
)
from SIL.LCModel.Core.KernelInterfaces import ITsString
from SIL.LCModel.Core.Text import TsStringUtils
from SIL.WritingSystems import WritingSystemDefinition  # Fixed: was IWritingSystemDefinition

# Import flexlibs exceptions
from ..FLExProject import (
    FP_ParameterError,
    FP_WritingSystemError,
)
from ..BaseOperations import BaseOperations, OperationsMethod


[docs] class ProjectSettingsOperations(BaseOperations): """ This class provides operations for managing project-level settings and configuration in a FieldWorks project. Project settings include metadata (name, description), language settings (vernacular and analysis writing systems), interface preferences, default fonts, and file paths for external resources. This class should be accessed via FLExProject.ProjectSettings property. Usage:: from flexlibs2 import FLExProject project = FLExProject() project.OpenProject("my project", writeEnabled=True) # Get project information name = project.ProjectSettings.GetProjectName() description = project.ProjectSettings.GetDescription("en") print(f"Project: {name}") print(f"Description: {description}") # Set project metadata project.ProjectSettings.SetProjectName("My Linguistic Study") project.ProjectSettings.SetDescription( "Documentation of endangered language", "en" ) # Language settings vern_wss = project.ProjectSettings.GetVernacularWSs() anal_wss = project.ProjectSettings.GetAnalysisWSs() print(f"Vernacular WSs: {vern_wss}") print(f"Analysis WSs: {anal_wss}") # Set default writing systems project.ProjectSettings.SetDefaultVernacular("qaa-x-kal") project.ProjectSettings.SetDefaultAnalysis("en") # Configure default fonts project.ProjectSettings.SetDefaultFont("qaa-x-kal", "Charis SIL") project.ProjectSettings.SetDefaultFontSize("qaa-x-kal", 14) # External file paths linked_path = project.ProjectSettings.GetLinkedFilesRootDir() print(f"Linked files: {linked_path}") # Metadata created = project.ProjectSettings.GetDateCreated() modified = project.ProjectSettings.GetDateModified() print(f"Created: {created}") print(f"Last modified: {modified}") project.CloseProject() """ def __init__(self, project): """ Initialize ProjectSettingsOperations with a FLExProject instance. Args: project: The FLExProject instance to operate on. """ super().__init__(project) # --- Project Information --- @OperationsMethod def GetProjectName(self): """ Get the project name. Returns: str: The name of the project Example: >>> name = project.ProjectSettings.GetProjectName() >>> print(f"Project name: {name}") Project name: Kalaba Documentation Notes: - Returns the display name of the project - This is different from the project filename - Empty string if no name is set See Also: SetProjectName, GetDescription """ if hasattr(self.project.lp, "Name") and self.project.lp.Name: ts = ITsString(self.project.lp.Name) return ts.Text if ts else "" return "" @OperationsMethod def SetProjectName(self, name): """ Set the project name. Args: name (str): The new project name Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If name is None FP_ParameterError: If name is empty Example: >>> project.ProjectSettings.SetProjectName("Kalaba Documentation") >>> name = project.ProjectSettings.GetProjectName() >>> print(name) Kalaba Documentation Notes: - Sets the display name of the project - Does not change the project filename - Name should be descriptive and meaningful See Also: GetProjectName, SetDescription """ self._EnsureWriteEnabled() self._ValidateParam(name, "name") if not name or not name.strip(): raise FP_ParameterError("Project name cannot be empty") # Get default analysis writing system for the name ws_handle = self.project.project.DefaultAnalWs ts = TsStringUtils.MakeString(name, ws_handle) self.project.lp.Name = ts @OperationsMethod def GetDescription(self, ws_handle_or_tag=None): """ Get the project description in the specified writing system. Args: ws_handle_or_tag: Writing system handle (int), language tag (str), or None for default analysis WS Returns: str: The project description text Raises: FP_WritingSystemError: If writing system not found Example: >>> # Get in default analysis WS >>> desc = project.ProjectSettings.GetDescription() >>> print(desc) Documentation of endangered Kalaba language >>> # Get in specific writing system >>> desc_en = project.ProjectSettings.GetDescription("en") >>> desc_fr = project.ProjectSettings.GetDescription("fr") Notes: - Returns empty string if no description set - Description can be set in multiple writing systems - Useful for multilingual project documentation See Also: SetDescription, GetProjectName """ ws_handle = self._ResolveWSHandle(ws_handle_or_tag) if hasattr(self.project.lp, "Description") and self.project.lp.Description: ts = ITsString(self.project.lp.Description.get_String(ws_handle)) return ts.Text if ts else "" return "" @OperationsMethod def SetDescription(self, description, ws_handle_or_tag=None): """ Set the project description in the specified writing system. Args: description (str): The description text ws_handle_or_tag: Writing system handle (int), language tag (str), or None for default analysis WS Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If description is None FP_WritingSystemError: If writing system not found Example: >>> # Set in default analysis WS >>> project.ProjectSettings.SetDescription( ... "Documentation of endangered Kalaba language" ... ) >>> # Set in multiple languages >>> project.ProjectSettings.SetDescription( ... "Documentation of endangered Kalaba language", "en" ... ) >>> project.ProjectSettings.SetDescription( ... "Documentation de la langue Kalaba en danger", "fr" ... ) Notes: - Can set different descriptions in different writing systems - Empty string clears the description - Description is used in project properties and reports See Also: GetDescription, SetProjectName """ self._EnsureWriteEnabled() self._ValidateParam(description, "description") ws_handle = self._ResolveWSHandle(ws_handle_or_tag) # Ensure Description MultiString exists if not self.project.lp.Description: # Description should exist, but handle gracefully logger.warning("Description MultiString not initialized") return ts = TsStringUtils.MakeString(description, ws_handle) self.project.lp.Description.set_String(ws_handle, ts) # --- Language Settings --- @OperationsMethod def GetVernacularWSs(self): """ Get the list of vernacular writing system tags. Returns: list: List of vernacular writing system language tags (e.g., ["qaa-x-kal"]) Example: >>> vern_wss = project.ProjectSettings.GetVernacularWSs() >>> print("Vernacular writing systems:") >>> for ws_tag in vern_wss: ... print(f" - {ws_tag}") Vernacular writing systems: - qaa-x-kal - qaa-x-kal-fonipa Notes: - Returns list of language tags, not WS objects - These are the writing systems for the object language(s) - Use WritingSystems operations to get full WS details - Empty list if no vernacular writing systems configured See Also: GetAnalysisWSs, SetDefaultVernacular """ if hasattr(self.project.lp, "CurVernWss") and self.project.lp.CurVernWss: return self.project.lp.CurVernWss.split() return [] @OperationsMethod def GetAnalysisWSs(self): """ Get the list of analysis writing system tags. Returns: list: List of analysis writing system language tags (e.g., ["en", "fr"]) Example: >>> anal_wss = project.ProjectSettings.GetAnalysisWSs() >>> print("Analysis writing systems:") >>> for ws_tag in anal_wss: ... print(f" - {ws_tag}") Analysis writing systems: - en - fr - es Notes: - Returns list of language tags, not WS objects - These are the writing systems for metadata and analysis - Use WritingSystems operations to get full WS details - Empty list if no analysis writing systems configured See Also: GetVernacularWSs, SetDefaultAnalysis """ if hasattr(self.project.lp, "CurAnalysisWss") and self.project.lp.CurAnalysisWss: return self.project.lp.CurAnalysisWss.split() return [] @OperationsMethod def SetDefaultVernacular(self, ws_handle_or_tag): """ Set the default vernacular writing system. Args: ws_handle_or_tag: Writing system handle (int) or language tag (str) Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If ws_handle_or_tag is None FP_WritingSystemError: If writing system not found FP_ParameterError: If WS is not in vernacular list Example: >>> # Set by language tag >>> project.ProjectSettings.SetDefaultVernacular("qaa-x-kal") >>> # Verify the change >>> default = project.WritingSystems.GetDefaultVernacular() >>> tag = project.WritingSystems.GetLanguageTag(default) >>> print(f"Default vernacular: {tag}") Default vernacular: qaa-x-kal Notes: - Writing system must be in the vernacular list - This is the primary WS for lexical entries - Used as fallback when no specific WS is specified See Also: SetDefaultAnalysis, GetVernacularWSs """ self._EnsureWriteEnabled() self._ValidateParam(ws_handle_or_tag, "ws_handle_or_tag") # Resolve to WS object ws = self._ResolveWS(ws_handle_or_tag) if not ws: raise FP_WritingSystemError(str(ws_handle_or_tag)) # Verify it's a vernacular WS vern_tags = set(self.GetVernacularWSs()) if ws.Id not in vern_tags: raise FP_ParameterError(f"Writing system '{ws.Id}' is not a vernacular writing system") # Set as default self.project.lp.DefaultVernacularWritingSystem = ws @OperationsMethod def SetDefaultAnalysis(self, ws_handle_or_tag): """ Set the default analysis writing system. Args: ws_handle_or_tag: Writing system handle (int) or language tag (str) Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If ws_handle_or_tag is None FP_WritingSystemError: If writing system not found FP_ParameterError: If WS is not in analysis list Example: >>> # Set by language tag >>> project.ProjectSettings.SetDefaultAnalysis("en") >>> # Verify the change >>> default = project.WritingSystems.GetDefaultAnalysis() >>> tag = project.WritingSystems.GetLanguageTag(default) >>> print(f"Default analysis: {tag}") Default analysis: en Notes: - Writing system must be in the analysis list - This is the primary WS for glosses and definitions - Used as fallback when no specific WS is specified See Also: SetDefaultVernacular, GetAnalysisWSs """ self._EnsureWriteEnabled() self._ValidateParam(ws_handle_or_tag, "ws_handle_or_tag") # Resolve to WS object ws = self._ResolveWS(ws_handle_or_tag) if not ws: raise FP_WritingSystemError(str(ws_handle_or_tag)) # Verify it's an analysis WS anal_tags = set(self.GetAnalysisWSs()) if ws.Id not in anal_tags: raise FP_ParameterError(f"Writing system '{ws.Id}' is not an analysis writing system") # Set as default self.project.lp.DefaultAnalysisWritingSystem = ws # --- UI Settings --- @OperationsMethod def GetInterfaceLanguage(self): """ Get the user interface language writing system. Returns: IWritingSystemDefinition: The UI language writing system Example: >>> ui_ws = project.ProjectSettings.GetInterfaceLanguage() >>> tag = project.WritingSystems.GetLanguageTag(ui_ws) >>> print(f"UI language: {tag}") UI language: en Notes: - This determines the language used in the FLEx interface - Typically an analysis writing system (e.g., "en", "fr", "es") - Falls back to default analysis WS if not specifically set See Also: SetInterfaceLanguage """ # The UI language is stored in UserWs if hasattr(self.project.lp, "UserWs"): return self.project.lp.UserWs # Fall back to default analysis return self.project.lp.DefaultAnalysisWritingSystem @OperationsMethod def SetInterfaceLanguage(self, ws_handle_or_tag): """ Set the user interface language writing system. Args: ws_handle_or_tag: Writing system handle (int) or language tag (str) Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If ws_handle_or_tag is None FP_WritingSystemError: If writing system not found Example: >>> # Set UI to French >>> project.ProjectSettings.SetInterfaceLanguage("fr") >>> # Set UI to English >>> project.ProjectSettings.SetInterfaceLanguage("en") Notes: - Changes the language used in the FLEx interface - Should typically be an analysis writing system - Requires application restart to take full effect See Also: GetInterfaceLanguage """ self._EnsureWriteEnabled() self._ValidateParam(ws_handle_or_tag, "ws_handle_or_tag") # Resolve to WS object ws = self._ResolveWS(ws_handle_or_tag) if not ws: raise FP_WritingSystemError(str(ws_handle_or_tag)) # Set the UI language if hasattr(self.project.lp, "UserWs"): self.project.lp.UserWs = ws # --- Default Font Settings --- @OperationsMethod def GetDefaultFont(self, ws_handle_or_tag): """ Get the default font name for a writing system. Args: ws_handle_or_tag: Writing system handle (int) or language tag (str) Returns: str: Font name (e.g., "Charis SIL", "Arial") Raises: FP_NullParameterError: If ws_handle_or_tag is None FP_WritingSystemError: If writing system not found Example: >>> font = project.ProjectSettings.GetDefaultFont("qaa-x-kal") >>> print(f"Default font: {font}") Default font: Charis SIL >>> # Get for analysis WS >>> font = project.ProjectSettings.GetDefaultFont("en") >>> print(font) Calibri Notes: - Returns the default font for display and editing - Empty string if no default font is set - This is a convenience wrapper around WritingSystems.GetFontName() See Also: SetDefaultFont, GetDefaultFontSize """ self._ValidateParam(ws_handle_or_tag, "ws_handle_or_tag") ws = self._ResolveWS(ws_handle_or_tag) if not ws: raise FP_WritingSystemError(str(ws_handle_or_tag)) # Get default font name if hasattr(ws, "DefaultFontName") and ws.DefaultFontName: return ws.DefaultFontName elif hasattr(ws, "DefaultFont") and ws.DefaultFont: return ws.DefaultFont return "" @OperationsMethod def SetDefaultFont(self, ws_handle_or_tag, font_name): """ Set the default font name for a writing system. Args: ws_handle_or_tag: Writing system handle (int) or language tag (str) font_name (str): Font name (e.g., "Charis SIL", "Arial") Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If ws_handle_or_tag or font_name is None FP_WritingSystemError: If writing system not found Example: >>> # Set font for vernacular >>> project.ProjectSettings.SetDefaultFont("qaa-x-kal", "Charis SIL") >>> # Set font for IPA >>> project.ProjectSettings.SetDefaultFont("qaa-x-kal-fonipa", "Doulos SIL") >>> # Set font for analysis >>> project.ProjectSettings.SetDefaultFont("en", "Calibri") Notes: - Font must be installed on the system - Changes affect all text displayed in this writing system - Recommended fonts for IPA: Charis SIL, Doulos SIL - This is a convenience wrapper around WritingSystems.SetFontName() See Also: GetDefaultFont, SetDefaultFontSize """ self._EnsureWriteEnabled() self._ValidateParam(ws_handle_or_tag, "ws_handle_or_tag") ws = self._ResolveWS(ws_handle_or_tag) if not ws: raise FP_WritingSystemError(str(ws_handle_or_tag)) # Set default font name if hasattr(ws, "DefaultFontName"): ws.DefaultFontName = font_name elif hasattr(ws, "DefaultFont"): ws.DefaultFont = font_name @OperationsMethod def GetDefaultFontSize(self, ws_handle_or_tag): """ Get the default font size for a writing system. Args: ws_handle_or_tag: Writing system handle (int) or language tag (str) Returns: float: Font size in points (e.g., 12.0) Raises: FP_NullParameterError: If ws_handle_or_tag is None FP_WritingSystemError: If writing system not found Example: >>> size = project.ProjectSettings.GetDefaultFontSize("qaa-x-kal") >>> print(f"Font size: {size} pt") Font size: 14.0 pt Notes: - Returns 12.0 if no default size is set - This is a convenience wrapper around WritingSystems.GetFontSize() See Also: SetDefaultFontSize, GetDefaultFont """ self._ValidateParam(ws_handle_or_tag, "ws_handle_or_tag") ws = self._ResolveWS(ws_handle_or_tag) if not ws: raise FP_WritingSystemError(str(ws_handle_or_tag)) # Get default font size if hasattr(ws, "DefaultFontSize") and ws.DefaultFontSize: return float(ws.DefaultFontSize) return 12.0 # Default size @OperationsMethod def SetDefaultFontSize(self, ws_handle_or_tag, size): """ Set the default font size for a writing system. Args: ws_handle_or_tag: Writing system handle (int) or language tag (str) size (float or int): Font size in points (e.g., 12, 14.5) Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If ws_handle_or_tag or size is None FP_WritingSystemError: If writing system not found FP_ParameterError: If size is not positive Example: >>> # Set standard size >>> project.ProjectSettings.SetDefaultFontSize("qaa-x-kal", 14) >>> # Set larger for better readability >>> project.ProjectSettings.SetDefaultFontSize("qaa-x-kal", 16) >>> # Set for analysis >>> project.ProjectSettings.SetDefaultFontSize("en", 12) Notes: - Size should typically be between 8 and 72 points - Common sizes: 10, 12, 14, 16 - Changes affect all text displayed in this writing system - This is a convenience wrapper around WritingSystems.SetFontSize() See Also: GetDefaultFontSize, SetDefaultFont """ self._EnsureWriteEnabled() self._ValidateParam(ws_handle_or_tag, "ws_handle_or_tag") if size <= 0: raise FP_ParameterError("Font size must be positive") ws = self._ResolveWS(ws_handle_or_tag) if not ws: raise FP_WritingSystemError(str(ws_handle_or_tag)) # Set default font size if hasattr(ws, "DefaultFontSize"): ws.DefaultFontSize = float(size) # --- Advanced Settings --- @OperationsMethod def GetLinkedFilesRootDir(self): """ Get the root directory for linked files (media, pictures, etc.). Returns: str: Path to the linked files root directory Example: >>> linked_path = project.ProjectSettings.GetLinkedFilesRootDir() >>> print(f"Linked files: {linked_path}") Linked files: C:\\Users\\username\\Documents\\FLEx\\Kalaba\\LinkedFiles Notes: - This is where FLEx stores media files, pictures, etc. - Path is typically relative to the project directory - Returns empty string if not set - Used for organizing external resources See Also: SetLinkedFilesRootDir, GetExtLinkRootDir """ if hasattr(self.project.lp, "LinkedFilesRootDir") and self.project.lp.LinkedFilesRootDir: return self.project.lp.LinkedFilesRootDir return "" @OperationsMethod def SetLinkedFilesRootDir(self, path): """ Set the root directory for linked files (media, pictures, etc.). Args: path (str): Path to the linked files root directory Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If path is None Example: >>> # Set absolute path >>> project.ProjectSettings.SetLinkedFilesRootDir( ... "C:\\\\Users\\\\username\\\\Documents\\\\FLEx\\\\Kalaba\\\\LinkedFiles" ... ) >>> # Set relative path >>> project.ProjectSettings.SetLinkedFilesRootDir("LinkedFiles") Notes: - Can be absolute or relative path - Relative paths are relative to the project directory - Directory should exist before setting - Used for organizing external resources like media files See Also: GetLinkedFilesRootDir, SetExtLinkRootDir """ self._EnsureWriteEnabled() self._ValidateParam(path, "path") self.project.lp.LinkedFilesRootDir = path @OperationsMethod def GetExtLinkRootDir(self): """ Get the external link root directory. Returns: str: Path to the external link root directory Example: >>> ext_link_path = project.ProjectSettings.GetExtLinkRootDir() >>> print(f"External links: {ext_link_path}") External links: C:\\Users\\username\\Documents\\ExternalResources Notes: - This is for external linked resources - Different from LinkedFilesRootDir - Returns empty string if not set - Used for organizing external reference materials See Also: SetExtLinkRootDir, GetLinkedFilesRootDir """ if hasattr(self.project.lp, "ExtLinkRootDir") and self.project.lp.ExtLinkRootDir: return self.project.lp.ExtLinkRootDir return "" @OperationsMethod def SetExtLinkRootDir(self, path): """ Set the external link root directory. Args: path (str): Path to the external link root directory Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If path is None Example: >>> # Set absolute path >>> project.ProjectSettings.SetExtLinkRootDir( ... "C:\\\\Users\\\\username\\\\Documents\\\\ExternalResources" ... ) >>> # Set relative path >>> project.ProjectSettings.SetExtLinkRootDir("ExternalLinks") Notes: - Can be absolute or relative path - Relative paths are relative to the project directory - Directory should exist before setting - Used for organizing external linked resources See Also: GetExtLinkRootDir, SetLinkedFilesRootDir """ self._EnsureWriteEnabled() self._ValidateParam(path, "path") self.project.lp.ExtLinkRootDir = path @OperationsMethod def GetAnalysisWritingSystems(self): """ Get the list of analysis writing system tags. Returns: list: List of analysis writing system language tags (e.g., ["en", "fr"]) Example: >>> anal_wss = project.ProjectSettings.GetAnalysisWritingSystems() >>> print("Analysis writing systems:") >>> for ws_tag in anal_wss: ... print(f" - {ws_tag}") Analysis writing systems: - en - fr - es Notes: - Returns list of language tags, not WS objects - These are the writing systems for metadata and analysis - READ-ONLY: Use other methods to modify the writing system list - Empty list if no analysis writing systems configured - This is an alias for GetAnalysisWSs() for consistency See Also: GetVernacularWritingSystems, GetAnalysisWSs """ if hasattr(self.project.lp, "AnalysisWss") and self.project.lp.AnalysisWss: return self.project.lp.AnalysisWss.split() return [] @OperationsMethod def GetVernacularWritingSystems(self): """ Get the list of vernacular writing system tags. Returns: list: List of vernacular writing system language tags (e.g., ["qaa-x-kal"]) Example: >>> vern_wss = project.ProjectSettings.GetVernacularWritingSystems() >>> print("Vernacular writing systems:") >>> for ws_tag in vern_wss: ... print(f" - {ws_tag}") Vernacular writing systems: - qaa-x-kal - qaa-x-kal-fonipa Notes: - Returns list of language tags, not WS objects - These are the writing systems for the object language(s) - READ-ONLY: Use other methods to modify the writing system list - Empty list if no vernacular writing systems configured - This is an alias for GetVernacularWSs() for consistency See Also: GetAnalysisWritingSystems, GetVernacularWSs """ if hasattr(self.project.lp, "CurVernWss") and self.project.lp.CurVernWss: return self.project.lp.CurVernWss.split() return [] @OperationsMethod def GetProjectGuid(self): """ Get the project GUID as a string. Returns: str: The project GUID (e.g., 'a1b2c3d4-e5f6-...'), or empty string if unavailable. Example: >>> guid = project.ProjectSettings.GetProjectGuid() >>> print(f"Project GUID: {guid}") Project GUID: a1b2c3d4-e5f6-7890-abcd-ef1234567890 Notes: - Derived from ILangProject.Guid - Stable identifier for this project instance - READ-ONLY See Also: GetProjectName """ if hasattr(self.project.lp, "Guid"): return str(self.project.lp.Guid) return "" @OperationsMethod def GetProjectDescription(self, ws_handle_or_tag=None): """ Get the project description in the specified writing system. Alias for GetDescription(). See GetDescription() for full documentation. Args: ws_handle_or_tag: Writing system handle (int), language tag (str), or None for default analysis WS. Returns: str: The project description text. See Also: GetDescription """ return self.GetDescription(ws_handle_or_tag) @OperationsMethod def GetExternalLink(self): """ Get the external (linked files) root directory path. Alias for GetExtLinkRootDir(). See GetExtLinkRootDir() for full documentation. Returns: str: The external link root directory path, or empty string if not set. See Also: GetExtLinkRootDir """ return self.GetExtLinkRootDir() @OperationsMethod def GetAnalysisWritingSystem(self): """ Get the default analysis writing system object. Returns: IWritingSystem: The default analysis writing system for this project, or None if the language project is unavailable. Example: >>> ws = project.ProjectSettings.GetAnalysisWritingSystem() >>> print(ws.Id) en Notes: - Returns the IWritingSystem object, not a string tag - For a list of all analysis writing system tags, use GetAnalysisWritingSystems() - READ-ONLY See Also: GetAnalysisWritingSystems, GetVernacularWritingSystem """ if self.project.lp is None: return None return self.project.lp.DefaultAnalysisWritingSystem @OperationsMethod def GetVernacularWritingSystem(self): """ Get the default vernacular writing system object. Returns: IWritingSystem: The default vernacular writing system for this project, or None if the language project is unavailable. Example: >>> ws = project.ProjectSettings.GetVernacularWritingSystem() >>> print(ws.Id) qaa-x-kal Notes: - Returns the IWritingSystem object, not a string tag - For a list of all vernacular writing system tags, use GetVernacularWritingSystems() - READ-ONLY See Also: GetVernacularWritingSystems, GetAnalysisWritingSystem """ if self.project.lp is None: return None return self.project.lp.DefaultVernacularWritingSystem # --- Project Metadata --- @OperationsMethod def GetDateCreated(self): """ Get the date the project was created. Returns: datetime: The creation date/time, or None if not available Example: >>> created = project.ProjectSettings.GetDateCreated() >>> if created: ... print(f"Created: {created.strftime('%Y-%m-%d %H:%M:%S')}") Created: 2024-01-15 10:30:00 Notes: - Returns .NET DateTime object - Can be None if creation date is not recorded - Read-only property See Also: GetDateModified """ if hasattr(self.project.lp, "DateCreated"): return self.project.lp.DateCreated return None @OperationsMethod def GetDateModified(self): """ Get the date the project was last modified. Returns: datetime: The last modification date/time, or None if not available Example: >>> modified = project.ProjectSettings.GetDateModified() >>> if modified: ... print(f"Last modified: {modified.strftime('%Y-%m-%d %H:%M:%S')}") Last modified: 2024-03-20 15:45:30 Notes: - Returns .NET DateTime object - Can be None if modification date is not recorded - Read-only property - Updated automatically when project is saved See Also: GetDateCreated """ if hasattr(self.project.lp, "DateModified"): return self.project.lp.DateModified return None # --- Private Helper Methods --- def _ResolveWSHandle(self, ws_handle_or_tag): """ Resolve a writing system parameter to a WS handle. Args: ws_handle_or_tag: Writing system handle (int), language tag (str), or None for default analysis WS Returns: int: Writing system handle Raises: FP_WritingSystemError: If writing system not found """ if ws_handle_or_tag is None: return self.project.project.DefaultAnalWs if isinstance(ws_handle_or_tag, str): # Language tag - resolve to handle ws = self._GetWSByTag(ws_handle_or_tag) if not ws: raise FP_WritingSystemError(ws_handle_or_tag) return ws.Handle else: # Assume it's a handle return ws_handle_or_tag def _ResolveWS(self, ws_handle_or_tag): """ Resolve a writing system parameter to a WS object. Args: ws_handle_or_tag: Writing system handle (int) or language tag (str) Returns: IWritingSystemDefinition or None: Writing system object or None """ self._ValidateParam(ws_handle_or_tag, "ws_handle_or_tag") if isinstance(ws_handle_or_tag, str): return self._GetWSByTag(ws_handle_or_tag) else: # Handle - resolve to WS object return self._GetWSByHandle(ws_handle_or_tag) def _GetWSByTag(self, language_tag): """ Get writing system object by language tag. Args: language_tag (str): Language tag Returns: IWritingSystemDefinition or None: Writing system or None if not found """ normalized_tag = self._NormalizeLangTag(language_tag) for ws in self.project.project.ServiceLocator.WritingSystems.AllWritingSystems: if self._NormalizeLangTag(ws.Id) == normalized_tag: return ws return None def _GetWSByHandle(self, handle): """ Get writing system object by handle. Args: handle (int): Writing system handle Returns: IWritingSystemDefinition or None: Writing system or None if not found """ for ws in self.project.project.ServiceLocator.WritingSystems.AllWritingSystems: if ws.Handle == handle: return ws return None def _NormalizeLangTag(self, language_tag): """ Normalize language tag for comparison (lowercase, underscores to hyphens). Args: language_tag (str): Language tag to normalize Returns: str: Normalized tag """ return language_tag.replace("_", "-").lower() @OperationsMethod def Duplicate(self, item_or_hvo, insert_after=True): """ Duplicate operation is not applicable for project settings. Project settings are singleton configuration objects and cannot be duplicated. Raises: NotImplementedError: Always raised - project settings cannot be duplicated. Notes: - Project settings are unique per project - Use getter/setter methods to modify settings - Settings are not duplicatable data objects See Also: GetProjectName, SetProjectName """ raise NotImplementedError( "Project settings cannot be duplicated. Project settings are singleton configuration objects." ) # ========== SYNC INTEGRATION METHODS ========== @OperationsMethod def GetSyncableProperties(self, item): """ Get syncable properties - NOT IMPLEMENTED for project settings. Project settings are configuration data unique to each project and should not be synced between projects. Raises: NotImplementedError: Project settings are not syncable """ raise NotImplementedError( "Project settings cannot be synced between projects. " "Settings are configuration data unique to each project." ) @OperationsMethod def CompareTo(self, item1, item2, ops1=None, ops2=None): """ Compare project settings - NOT IMPLEMENTED. Project settings are configuration data unique to each project and should not be synced between projects. Raises: NotImplementedError: Project settings are not syncable """ raise NotImplementedError( "Project settings cannot be compared for sync. " "Settings are configuration data unique to each project." )