mindm.mindmanager_mac_as module

MacOS-specific implementation of the Mindmanager interface. This class implementation uses the native AppleScript approach.

This module provides MacOS platform-specific implementation for interacting with MindManager application, including functionality for manipulating topics, properties, relationships, and document structure.

class mindm.mindmanager_mac_as.Mindmanager(charttype)

Bases: object

MACOS_LIBRARY_FOLDER = 'C:\\Users\\runneradmin\\Library\\Application Support\\Mindjet\\MindManager\\XX\\English\\Library'
MACOS_MERGE_ALL_WINDOWS = False
__init__(charttype)
add_document(max_topic_level)

Opens the correct template based on charttype and subtopic counts.

add_relationship(guid1, guid2, label='')
add_subtopic_to_topic(topic_id, topic_text)

Create a new subtopic under topic_id with topic_text. Return the new subtopic’s ID or None on failure.

add_tag_to_topic(topic_id, tag_text, topic_guid)
create_map_icons(map_icons)
create_tags(tags: list[str], DUPLICATED_TAG: str)
document_exists()

Returns True if there’s at least one open document in MindManager.

finalize(max_topic_level)

Balance the map, activate MindManager, optionally merge windows, and clean up.

get_active_document_object()

Return ‘document 1’ if a document is open, else None.

get_central_topic() MindmapTopic

Return the central topic’s ID or None if not found.

get_guid_from_topic(topic_id) str
get_icons_from_topic(topic_id) list[MindmapIcon]
get_image_from_topic(topic_id) MindmapImage
get_level_from_topic(topic)
get_library_folder()
get_mindmanager_object()
get_mindmaptopic_from_topic(topic) MindmapTopic

Returns a MindmapTopic with guid, text, rtf and level, all retrieved via a single AppleScript call.

get_mindmaptopic_from_topic_content(topic_id) MindmapTopic

Returns a MindmapTopic with guid, text, rtf, level, and notes, all retrieved via a single AppleScript call.

get_mindmaptopic_from_topic_full(topic_id) MindmapTopic

Returns a MindmapTopic with guid, text, rtf, level, notes, and references, all via one AppleScript call. (links/icons/tags/image remain unimplemented.)

get_notes_from_topic(topic_id) MindmapNotes

Return MindmapNotes or None.

get_parent_from_topic(topic_id)

Return the parent’s ID or None if there is no parent or the topic doesn’t exist.

get_references_from_topic(topic_id) list[MindmapReference]

Return a list of MindmapReference objects for the given topic.

get_selection()

Return a list of topic IDs in the current selection.

get_subtopics_from_topic(topic_id)

Return a list of subtopic IDs.

get_tags_from_topic(topic_id) list[MindmapTag]
get_text_from_topic(topic_id)
get_title_from_topic(topic_id)
get_topic_by_id(topic_id)
get_version()
set_document_background_image(path)
set_text_to_topic(topic_id, topic_text)

Set the topic’s text (equivalent to topic.name.set).

set_title_to_topic(topic_id, topic_rtf)

Set the topic’s title (equivalent to topic.title.set).

set_topic_from_mindmap_topic(topic_id, mindmap_topic, map_icons)

Updates the topic’s text, RTF title, and notes from mindmap_topic via a single AppleScript call. Returns (refreshed_topic_id, original_topic_id).

Source code for mindmanager_mac_as.py
  1"""
  2MacOS-specific implementation of the Mindmanager interface.
  3This class implementation uses the native AppleScript approach.
  4
  5This module provides MacOS platform-specific implementation for interacting
  6with MindManager application, including functionality for manipulating topics,
  7properties, relationships, and document structure.
  8"""
  9
 10import os
 11import sys
 12import json
 13import subprocess
 14
 15from mindmap.mindmap import (
 16    MindmapLink,
 17    MindmapImage,
 18    MindmapNotes,
 19    MindmapIcon,
 20    MindmapTag,
 21    MindmapReference,
 22    MindmapTopic,
 23)
 24import mindmap.serialization as mms
 25
 26APPLESCRIPT_READ = os.path.join(os.path.dirname(__file__), "as", "read.scpt")
 27APPLESCRIPT_WRITE = os.path.join(os.path.dirname(__file__), "as", "write.scpt")
 28
 29def _run_applescript(script: str, args: list = None) -> str:
 30    if args is None:
 31        args = []
 32    
 33    command = ["osascript", "-e", script] + args
 34    try:
 35        result = subprocess.run(
 36            command,
 37            capture_output=True,
 38            text=True,
 39            check=True
 40        )
 41        return result.stdout.strip()
 42    except subprocess.CalledProcessError as e:
 43        print(f"AppleScript error: {e.stderr}")
 44        return ""
 45
 46def _run_compiled_applescript(script_path: str, args: list = None) -> str:
 47    if args is None:
 48        args = []
 49    
 50    command = ["osascript", script_path] + (args or [])
 51    
 52    try:
 53        result = subprocess.run(
 54            command,
 55            capture_output=True,
 56            text=True,
 57            check=True
 58        )
 59        return result.stdout.strip()
 60    except subprocess.CalledProcessError as e:
 61        print("osascript returned", e.returncode)
 62        print("--- stdout ---\n", e.stdout)
 63        print("--- stderr ---\n", e.stderr)
 64        return ""
 65    
 66class Mindmanager:
 67
 68    MACOS_MERGE_ALL_WINDOWS = False
 69    MACOS_LIBRARY_FOLDER = os.path.join(
 70        os.path.expanduser("~"), 
 71        "Library", 
 72        "Application Support", 
 73        "Mindjet", 
 74        "MindManager", 
 75        "XX", 
 76        "English", 
 77        "Library"
 78    )
 79
 80    def __init__(self, charttype):
 81        self._charttype = charttype
 82
 83        # Get version from MindManager
 84        script = 'tell application "MindManager" to return version'
 85        version_str = _run_applescript(script)
 86        if version_str:
 87            self._version = version_str.split('.')[0]
 88        else:
 89            self._version = "0"
 90
 91        self._library_folder = self.MACOS_LIBRARY_FOLDER.replace("XX", self._version)
 92        if self._version == '24':
 93            orgchart_template_path = os.path.join(self._library_folder, "Templates", "Blank Templates", "Organization Chart.mmat")
 94        else:
 95            orgchart_template_path = os.path.join(self._library_folder, "Templates", "Blank Templates", "Org-Chart Map.mmat")
 96        self._orgchart_template = orgchart_template_path
 97        self._radial_template = os.path.join(self._library_folder, "Templates", "Blank Templates", "Radial Map.mmat")
 98
 99    def _read(self, modus, arguments = None) -> list['MindmapTopic']:
100        if arguments:
101             script_args = [modus] + arguments
102        else:
103            script_args = [modus]
104
105        json_string = _run_compiled_applescript(APPLESCRIPT_READ, script_args)
106    
107        if not json_string:
108            return None  # no data returned
109        try:
110            jsonObject = json.loads(json_string)
111            if isinstance(jsonObject, dict):
112                # It's a single tree structure
113                topic = self._dict_to_mindmap_topic(jsonObject)
114                return [topic] if topic else None
115            elif isinstance(jsonObject, list):
116                # It's a list of topic structures
117                topics = []
118                for item in jsonObject:
119                    if isinstance(item, dict):
120                        topic = self._dict_to_mindmap_topic(item)
121                        if topic:
122                            topics.append(topic)
123                return topics
124            else:
125                # Unexpected format
126                print(f"Unexpected JSON format received: {type(jsonObject)}")
127                return None
128        except json.JSONDecodeError as e:
129            print(f"Error parsing JSON from AppleScript: {e}")
130            return None
131    
132    def _write(self, modus, mindmap_topic):
133        json_string = json.dumps(mms.serialize_object_simple(mindmap_topic))
134        script_args = [modus] + [json_string]
135        result = _run_compiled_applescript(APPLESCRIPT_WRITE, script_args)
136        return result
137
138    def _dict_to_mindmap_topic(self, node_dict: dict) -> 'MindmapTopic':
139        """
140        Helper to recursively convert a dict of the form:
141        { "guid": ..., "text": ..., "level": ..., "notes": ..., "subtopics": [ ... ] }
142        into a MindmapTopic object with subtopics.
143        """
144        notes_obj = None
145        if node_dict.get("notes"):
146            notes_obj = MindmapNotes(text=node_dict["notes"])
147        references = []
148        for ref in node_dict.get("references", []) or []:
149            if not isinstance(ref, dict):
150                continue
151            direction = 0
152            try:
153                direction = int(ref.get("direction", 0) or 0)
154            except (TypeError, ValueError):
155                direction = 0
156            references.append(
157                MindmapReference(
158                    direction=direction,
159                    guid_1=ref.get("guid_1", ""),
160                    guid_2=ref.get("guid_2", ""),
161                )
162            )
163        tags = []
164        for tag in node_dict.get("tags", []) or []:
165            if isinstance(tag, dict):
166                text_val = tag.get("text", "")
167            else:
168                text_val = tag
169            if text_val:
170                tags.append(MindmapTag(text=text_val))
171        topic = MindmapTopic(
172            guid=node_dict.get("guid", ""),
173            text=node_dict.get("text", ""),
174            level=int(node_dict.get("level", 0)),
175            notes=notes_obj,
176            tags=tags,
177            references=references,
178        )
179        subtopics = node_dict.get("subtopics", [])
180        for child_dict in subtopics:
181            child_topic = self._dict_to_mindmap_topic(child_dict)
182            if child_topic:
183                topic.subtopics.append(child_topic)
184                child_topic.parent = topic
185
186        return topic
187    
188    def get_mindmanager_object(self):
189        return None
190
191    def get_active_document_object(self):
192        """
193        Return 'document 1' if a document is open, else None.
194        """
195        return "document 1" if self.document_exists() else None
196
197    def get_library_folder(self):
198        return self._library_folder
199
200    def get_version(self):
201        return self._version
202
203    def set_document_background_image(self, path):
204        pass
205
206    def document_exists(self):
207        """
208        Returns True if there's at least one open document in MindManager.
209        """
210        script = '''
211            tell application "MindManager"
212                if (count of documents) > 0 then
213                    return "true"
214                else
215                    return "false"
216                end if
217            end tell
218        '''
219        result = _run_applescript(script)
220        return (result == "true")
221
222    def get_central_topic(self) -> 'MindmapTopic':
223        """
224        Return the central topic's ID or None if not found.
225        """
226        result = self._read("getTree")
227        return result[0] if result else None
228
229    def get_mindmaptopic_from_topic(self, topic) -> 'MindmapTopic':
230        """
231        Returns a MindmapTopic with guid, text, rtf and level,
232        all retrieved via a single AppleScript call.
233        """
234        if not topic:
235            return None
236        topic_id = topic.guid
237        if not topic_id:
238            return None
239
240        # Single AppleScript to grab all basic properties at once:
241        script = f'''
242            tell application "MindManager"
243                try
244                    set theTopic to first topic of document 1 whose id is "{topic_id}"
245                    set theGUID to id of theTopic
246                    set theName to name of theTopic
247                    set theTitle to title of theTopic
248                    set theLevel to level of theTopic
249                    return theGUID & "%%" & theName & "%%" & theTitle & "%%" & (theLevel as text)
250                on error
251                    return ""
252                end try
253            end tell
254        '''
255        result = _run_applescript(script)
256        if not result:
257            return None  # topic not found or error
258
259        parts = result.split("%%", 3)  # we expect exactly 4 parts
260        if len(parts) < 4:
261            return None
262
263        theGUID, theName, theTitle, theLevelStr = parts
264
265        # Convert level to integer if possible
266        try:
267            theLevel = int(theLevelStr)
268        except ValueError:
269            theLevel = None
270
271        # Clean up the text property so it mimics your old replacements
272        theName = theName.replace('"', '`').replace("'", "`").replace("\r", "").replace("\n", "")
273
274        # Construct and return the MindmapTopic
275        return MindmapTopic(
276            guid=theGUID,
277            text=theName,
278            rtf=theTitle,
279            level=theLevel,
280        )
281
282    def get_mindmaptopic_from_topic_content(self, topic_id) -> 'MindmapTopic':
283        """
284        Returns a MindmapTopic with guid, text, rtf, level, and notes,
285        all retrieved via a single AppleScript call.
286        """
287        if not topic_id:
288            return None
289
290        # Single AppleScript to grab all basic properties at once:
291        script = f'''
292            tell application "MindManager"
293                try
294                    set theTopic to first topic of document 1 whose id is "{topic_id}"
295                    set theGUID to id of theTopic
296                    set theName to name of theTopic
297                    set theTitle to title of theTopic
298                    set theLevel to level of theTopic
299                    set theNotes to notes of theTopic
300                    return theGUID & "%%" & theName & "%%" & theTitle & "%%" & (theLevel as text) & "%%" & theNotes
301                on error
302                    return ""
303                end try
304            end tell
305        '''
306        result = _run_applescript(script)
307        if not result:
308            return None  # topic not found or error
309
310        parts = result.split("%%", 4)  # we expect exactly 5 parts
311        if len(parts) < 5:
312            return None
313
314        theGUID, theName, theTitle, theLevelStr, theNotes = parts
315
316        # Convert level to integer if possible
317        try:
318            theLevel = int(theLevelStr)
319        except ValueError:
320            theLevel = None
321
322        # Clean up the text property so it mimics your old replacements
323        theName = theName.replace('"', '`').replace("'", "`").replace("\r", "").replace("\n", "")
324
325        # Build the MindmapNotes object if notes are non-empty
326        notes_obj = MindmapNotes(text=theNotes) if theNotes else None
327
328        # Construct and return the MindmapTopic
329        return MindmapTopic(
330            guid=theGUID,
331            text=theName,
332            rtf=theTitle,
333            level=theLevel,
334            notes=notes_obj,
335        )
336
337    def get_mindmaptopic_from_topic_full(self, topic_id) -> 'MindmapTopic':
338        """
339        Returns a MindmapTopic with guid, text, rtf, level, notes, and references,
340        all via one AppleScript call. (links/icons/tags/image remain unimplemented.)
341        """
342        if not topic_id:
343            return None
344
345        # Single AppleScript to grab all properties + references
346        script = f'''
347            tell application "MindManager"
348                try
349                    set theTopic to first topic of document 1 whose id is "{topic_id}"
350                    set theGUID to id of theTopic
351                    set theName to name of theTopic
352                    set theTitle to title of theTopic
353                    set theLevel to level of theTopic
354                    set theNotes to notes of theTopic
355                    set rels to relationships of theTopic
356                    set referencesString to ""
357                    repeat with r in rels
358                        set sLoc to id of (starting location of r)
359                        set eLoc to id of (ending location of r)
360                        set referencesString to referencesString & sLoc & "||" & eLoc & "||--||"
361                    end repeat
362                    return theGUID & "%%" & theName & "%%" & theTitle & "%%" & (theLevel as text) & "%%" & theNotes & "%%" & referencesString
363                on error
364                    return ""
365                end try
366            end tell
367        '''
368        result = _run_applescript(script)
369        if not result:
370            return None
371
372        # We expect 6 parts: guid, name, title, level, notes, referencesString
373        parts = result.split("%%", 5)
374        if len(parts) < 6:
375            return None
376
377        theGUID, theName, theTitle, theLevelStr, theNotes, referencesRaw = parts
378
379        # Convert level to integer if possible
380        try:
381            theLevel = int(theLevelStr)
382        except ValueError:
383            theLevel = None
384
385        # Clean up the text property
386        theName = theName.replace('"', '`').replace("'", "`").replace("\r", "").replace("\n", "")
387
388        # Build the MindmapNotes object if notes are non-empty
389        notes_obj = MindmapNotes(text=theNotes) if theNotes else None
390
391        # Parse references:
392        # referencesRaw might look like "GUID1||GUID2||--||GUID3||GUID4||--||"
393        references = []
394        if referencesRaw:
395            rel_chunks = referencesRaw.split("||--||")
396            for chunk in rel_chunks:
397                chunk = chunk.strip()
398                if not chunk:
399                    continue
400                pair = chunk.split("||")
401                if len(pair) == 2:
402                    sLoc, eLoc = pair
403                    if sLoc == theGUID:  # If it matches the old pattern
404                        references.append(
405                            MindmapReference(direction=1, guid_1=sLoc, guid_2=eLoc)
406                        )
407                    else:
408                        # Or handle direction=2 or other logic if needed
409                        pass
410
411        # For now, links, icons, tags, image remain unimplemented => empty
412        links = []
413        icons = []
414        tags = []
415        image = None
416
417        return MindmapTopic(
418            guid=theGUID,
419            text=theName,
420            rtf=theTitle,
421            level=theLevel,
422            notes=notes_obj,
423            links=links,
424            image=image,
425            icons=icons,
426            tags=tags,
427            references=references,
428        )
429    
430    def get_topic_by_id(self, topic_id):
431        return topic_id
432
433    def get_selection(self):
434        """
435        Return a list of topic IDs in the current selection.
436        """
437        result = self._read("getSelection")
438        return result
439
440    def get_level_from_topic(self, topic):
441        if not topic:
442            return None
443        topic_id = topic.guid
444        if not topic_id:
445            return None
446        script = f'''
447            tell application "MindManager"
448                try
449                    set theTopic to first topic of document 1 whose id is "{topic_id}"
450                    return level of theTopic
451                on error
452                    return ""
453                end try
454            end tell
455        '''
456        level_str = _run_applescript(script)
457        return int(level_str) if level_str.isdigit() else None
458
459    def get_text_from_topic(self, topic_id):
460        if not topic_id:
461            return ""
462        script = f'''
463            tell application "MindManager"
464                try
465                    set theTopic to first topic of document 1 whose id is "{topic_id}"
466                    return name of theTopic
467                on error
468                    return ""
469                end try
470            end tell
471        '''
472        text = _run_applescript(script)
473        # Replace certain characters (as in original code)
474        text = text.replace('"', '`').replace("'", "`").replace("\r", "").replace("\n", "")
475        return text
476
477    def get_title_from_topic(self, topic_id):
478        if not topic_id:
479            return ""
480        script = f'''
481            tell application "MindManager"
482                try
483                    set theTopic to first topic of document 1 whose id is "{topic_id}"
484                    return title of theTopic
485                on error
486                    return ""
487                end try
488            end tell
489        '''
490        return _run_applescript(script)
491
492    def get_subtopics_from_topic(self, topic_id):
493        """
494        Return a list of subtopic IDs.
495        """
496        if not topic_id:
497            return []
498        script = f'''
499            tell application "MindManager"
500                try
501                    set theTopic to first topic of document 1 whose id is "{topic_id}"
502                    set subTs to subtopics of theTopic
503                    set output to ""
504                    repeat with t in subTs
505                        set output to output & (id of t) & linefeed
506                    end repeat
507                    return output
508                on error
509                    return ""
510                end try
511            end tell
512        '''
513        raw = _run_applescript(script)
514        return [x.strip() for x in raw.splitlines() if x.strip()]
515
516    def get_links_from_topic(self, topic_id) -> list[MindmapLink]:
517        return []
518
519    def get_image_from_topic(self, topic_id) -> MindmapImage:
520        return None
521
522    def get_icons_from_topic(self, topic_id) -> list[MindmapIcon]:
523        return []
524
525    def get_notes_from_topic(self, topic_id) -> MindmapNotes:
526        """
527        Return MindmapNotes or None.
528        """
529        if not topic_id:
530            return None
531        script = f'''
532            tell application "MindManager"
533                try
534                    set theTopic to first topic of document 1 whose id is "{topic_id}"
535                    return notes of theTopic
536                on error
537                    return ""
538                end try
539            end tell
540        '''
541        notes_text = _run_applescript(script)
542        if notes_text:
543            return MindmapNotes(text=notes_text)
544        return None
545
546    def get_tags_from_topic(self, topic_id) -> list[MindmapTag]:
547        return []
548
549    def get_references_from_topic(self, topic_id) -> list[MindmapReference]:
550        """
551        Return a list of MindmapReference objects for the given topic.
552        """
553        references = []
554        if not topic_id:
555            return references
556
557        script = f'''
558            tell application "MindManager"
559                try
560                    set theTopic to first topic of document 1 whose id is "{topic_id}"
561                    set rels to relationships of theTopic
562                    if (count of rels) = 0 then
563                        return ""
564                    end if
565                    set outList to ""
566                    repeat with r in rels
567                        set sLoc to id of (starting location of r)
568                        set eLoc to id of (ending location of r)
569                        set outList to outList & sLoc & "||" & eLoc & linefeed
570                    end repeat
571                    return outList
572                on error
573                    return ""
574                end try
575            end tell
576        '''
577        raw = _run_applescript(script)
578        for line in raw.splitlines():
579            parts = line.split("||")
580            if len(parts) == 2:
581                sLoc, eLoc = parts
582                if sLoc == topic_id:
583                    references.append(
584                        MindmapReference(
585                            direction=1,
586                            guid_1=sLoc,
587                            guid_2=eLoc
588                        )
589                    )
590        return references
591
592    def get_guid_from_topic(self, topic_id) -> str:
593        return topic_id if topic_id else ""
594
595    def add_subtopic_to_topic(self, topic_id, topic_text):
596        """
597        Create a new subtopic under `topic_id` with `topic_text`.
598        Return the new subtopic's ID or None on failure.
599        """
600        if not topic_id:
601            return None
602        safe_text = topic_text.replace('"', '\\"')
603        script = f'''
604            tell application "MindManager"
605                try
606                    set parentTopic to first topic of document 1 whose id is "{topic_id}"
607                    set newT to make new topic at end of subtopics of parentTopic with properties {{name:"{safe_text}"}}
608                    return id of newT
609                on error
610                    return ""
611                end try
612            end tell
613        '''
614        new_id = _run_applescript(script)
615        return new_id if new_id else None
616
617    def get_parent_from_topic(self, topic_id):
618        """
619        Return the parent's ID or None if there is no parent or the topic doesn't exist.
620        """
621        if not topic_id:
622            return None
623        script = f'''
624            tell application "MindManager"
625                try
626                    set theTopic to first topic of document 1 whose id is "{topic_id}"
627                    set p to parent of theTopic
628                    if p is not missing value then
629                        return id of p
630                    else
631                        return ""
632                    end if
633                on error
634                    return ""
635                end try
636            end tell
637        '''
638        result = _run_applescript(script)
639        return result if result else None
640
641    def set_text_to_topic(self, topic_id, topic_text):
642        """
643        Set the topic's text (equivalent to topic.name.set).
644        """
645        if not topic_id:
646            return
647        safe_text = topic_text.replace('"', '\\"')
648        script = f'''
649            tell application "MindManager"
650                try
651                    set theTopic to first topic of document 1 whose id is "{topic_id}"
652                    set name of theTopic to "{safe_text}"
653                end try
654            end tell
655        '''
656        _run_applescript(script)
657
658    def set_title_to_topic(self, topic_id, topic_rtf):
659        """
660        Set the topic's title (equivalent to topic.title.set).
661        """
662        if not topic_id:
663            return
664        safe_text = topic_rtf.replace('"', '\\"')
665        script = f'''
666            tell application "MindManager"
667                try
668                    set theTopic to first topic of document 1 whose id is "{topic_id}"
669                    set title of theTopic to "{safe_text}"
670                end try
671            end tell
672        '''
673        _run_applescript(script)
674
675    def add_tag_to_topic(self, topic_id, tag_text, topic_guid):
676        pass
677
678    def set_topic_from_mindmap_topic(self, topic_id, mindmap_topic, map_icons):
679        """
680        Updates the topic's text, RTF title, and notes from `mindmap_topic` 
681        via a single AppleScript call. 
682        Returns (refreshed_topic_id, original_topic_id).
683        """
684        if not topic_id and not mindmap_topic:
685            return None, None
686        
687        if not topic_id:
688            try:
689                self._write("writeTree", mindmap_topic)
690                return None, None
691
692            except Exception as e:
693                print(f"Error in set_topic_from_mindmap_topic: {e}")
694                return None, None
695        else:
696            try:
697                script_lines = []
698                script_lines.append('tell application "MindManager"')
699                script_lines.append('    try')
700                script_lines.append(f'        set theTopic to first topic of document 1 whose id is "{topic_id}"')
701                safe_text = (mindmap_topic.text or "").replace('"', '\\"')
702                script_lines.append(f'        set name of theTopic to "{safe_text}"')
703                if mindmap_topic.rtf:
704                    safe_rtf = mindmap_topic.rtf.replace('"', '\\"')
705                    script_lines.append(f'        set title of theTopic to "{safe_rtf}"')
706                if mindmap_topic.notes:
707                    safe_notes = (mindmap_topic.notes.text or "").replace('"', '\\"')
708                    script_lines.append(f'        set notes of theTopic to "{safe_notes}"')
709                script_lines.append('        return id of theTopic')
710                script_lines.append('    on error errMsg')
711                script_lines.append('        return ""')
712                script_lines.append('    end try')
713                script_lines.append('end tell')
714                full_script = "\n".join(script_lines)
715                refreshed_id = _run_applescript(full_script)
716                if not refreshed_id:
717                    return None, None
718                return refreshed_id, topic_id
719
720            except Exception as e:
721                print(f"Error in set_topic_from_mindmap_topic: {e}")
722                return None, None
723
724    def create_map_icons(self, map_icons):
725        pass
726
727    def create_tags(self, tags: list[str], DUPLICATED_TAG: str):
728        pass
729
730    def add_relationship(self, guid1, guid2, label=''):
731        if not guid1 or not guid2:
732            print("Error in add_relationship: One or both topic IDs missing.")
733            return
734        script = f'''
735            tell application "MindManager"
736                try
737                    set t1 to first topic of document 1 whose id is "{guid1}"
738                    set t2 to first topic of document 1 whose id is "{guid2}"
739                    if t1 is not missing value and t2 is not missing value then
740                        make new relationship with properties {{starting location:t1, ending location:t2}}
741                    end if
742                on error errMsg
743                    return ""
744                end try
745            end tell
746        '''
747        _run_applescript(script)
748
749    def add_topic_link(self, guid1, guid2, label=''):
750        pass
751
752    def add_document(self, max_topic_level):
753        """
754        Opens the correct template based on charttype and subtopic counts.
755        """
756        if not self.document_exists():
757            cnt_subtopics = 0
758        else:
759            script_count = '''
760                tell application "MindManager"
761                    set c to count of subtopics of central topic of document 1
762                    return c
763                end tell
764            '''
765            res = _run_applescript(script_count)
766            try:
767                cnt_subtopics = int(res)
768            except:
769                cnt_subtopics = 0
770
771        if self._charttype == "orgchart":
772            template_alias = self._orgchart_template
773        elif self._charttype == "radial":
774            template_alias = self._radial_template
775        else:
776            # "auto"
777            if max_topic_level > 2 and cnt_subtopics > 4:
778                template_alias = self._orgchart_template
779            else:
780                template_alias = self._radial_template
781
782        safe_path = template_alias.replace('"', '\\"')
783        script_open = f'''
784            tell application "MindManager"
785                open POSIX file "{safe_path}"
786            end tell
787        '''
788        _run_applescript(script_open)
789
790    def finalize(self, max_topic_level):
791        """
792        Balance the map, activate MindManager, optionally merge windows, and clean up.
793        """
794        if not self.document_exists():
795            return
796
797        # Balance map
798        script_balance = '''
799            tell application "MindManager"
800                try
801                    balance map of document 1
802                end try
803            end tell
804        '''
805        _run_applescript(script_balance)
806
807        # Activate MindManager
808        script_activate = '''
809            tell application "MindManager"
810                activate
811            end tell
812        '''
813        _run_applescript(script_activate)
814
815        # Optionally merge all windows
816        if self.MACOS_MERGE_ALL_WINDOWS:
817            self.merge_windows()
818
819        # No persistent object references to clear
820        pass