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_id)
get_library_folder()
get_mindmanager_object()
get_mindmaptopic_from_topic(topic_id) 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_id) -> '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_id:
235            return None
236
237        # Single AppleScript to grab all basic properties at once:
238        script = f'''
239            tell application "MindManager"
240                try
241                    set theTopic to first topic of document 1 whose id is "{topic_id}"
242                    set theGUID to id of theTopic
243                    set theName to name of theTopic
244                    set theTitle to title of theTopic
245                    set theLevel to level of theTopic
246                    return theGUID & "%%" & theName & "%%" & theTitle & "%%" & (theLevel as text)
247                on error
248                    return ""
249                end try
250            end tell
251        '''
252        result = _run_applescript(script)
253        if not result:
254            return None  # topic not found or error
255
256        parts = result.split("%%", 3)  # we expect exactly 4 parts
257        if len(parts) < 4:
258            return None
259
260        theGUID, theName, theTitle, theLevelStr = parts
261
262        # Convert level to integer if possible
263        try:
264            theLevel = int(theLevelStr)
265        except ValueError:
266            theLevel = None
267
268        # Clean up the text property so it mimics your old replacements
269        theName = theName.replace('"', '`').replace("'", "`").replace("\r", "").replace("\n", "")
270
271        # Construct and return the MindmapTopic
272        return MindmapTopic(
273            guid=theGUID,
274            text=theName,
275            rtf=theTitle,
276            level=theLevel,
277        )
278
279    def get_mindmaptopic_from_topic_content(self, topic_id) -> 'MindmapTopic':
280        """
281        Returns a MindmapTopic with guid, text, rtf, level, and notes,
282        all retrieved via a single AppleScript call.
283        """
284        if not topic_id:
285            return None
286
287        # Single AppleScript to grab all basic properties at once:
288        script = f'''
289            tell application "MindManager"
290                try
291                    set theTopic to first topic of document 1 whose id is "{topic_id}"
292                    set theGUID to id of theTopic
293                    set theName to name of theTopic
294                    set theTitle to title of theTopic
295                    set theLevel to level of theTopic
296                    set theNotes to notes of theTopic
297                    return theGUID & "%%" & theName & "%%" & theTitle & "%%" & (theLevel as text) & "%%" & theNotes
298                on error
299                    return ""
300                end try
301            end tell
302        '''
303        result = _run_applescript(script)
304        if not result:
305            return None  # topic not found or error
306
307        parts = result.split("%%", 4)  # we expect exactly 5 parts
308        if len(parts) < 5:
309            return None
310
311        theGUID, theName, theTitle, theLevelStr, theNotes = parts
312
313        # Convert level to integer if possible
314        try:
315            theLevel = int(theLevelStr)
316        except ValueError:
317            theLevel = None
318
319        # Clean up the text property so it mimics your old replacements
320        theName = theName.replace('"', '`').replace("'", "`").replace("\r", "").replace("\n", "")
321
322        # Build the MindmapNotes object if notes are non-empty
323        notes_obj = MindmapNotes(text=theNotes) if theNotes else None
324
325        # Construct and return the MindmapTopic
326        return MindmapTopic(
327            guid=theGUID,
328            text=theName,
329            rtf=theTitle,
330            level=theLevel,
331            notes=notes_obj,
332        )
333
334    def get_mindmaptopic_from_topic_full(self, topic_id) -> 'MindmapTopic':
335        """
336        Returns a MindmapTopic with guid, text, rtf, level, notes, and references,
337        all via one AppleScript call. (links/icons/tags/image remain unimplemented.)
338        """
339        if not topic_id:
340            return None
341
342        # Single AppleScript to grab all properties + references
343        script = f'''
344            tell application "MindManager"
345                try
346                    set theTopic to first topic of document 1 whose id is "{topic_id}"
347                    set theGUID to id of theTopic
348                    set theName to name of theTopic
349                    set theTitle to title of theTopic
350                    set theLevel to level of theTopic
351                    set theNotes to notes of theTopic
352                    set rels to relationships of theTopic
353                    set referencesString to ""
354                    repeat with r in rels
355                        set sLoc to id of (starting location of r)
356                        set eLoc to id of (ending location of r)
357                        set referencesString to referencesString & sLoc & "||" & eLoc & "||--||"
358                    end repeat
359                    return theGUID & "%%" & theName & "%%" & theTitle & "%%" & (theLevel as text) & "%%" & theNotes & "%%" & referencesString
360                on error
361                    return ""
362                end try
363            end tell
364        '''
365        result = _run_applescript(script)
366        if not result:
367            return None
368
369        # We expect 6 parts: guid, name, title, level, notes, referencesString
370        parts = result.split("%%", 5)
371        if len(parts) < 6:
372            return None
373
374        theGUID, theName, theTitle, theLevelStr, theNotes, referencesRaw = parts
375
376        # Convert level to integer if possible
377        try:
378            theLevel = int(theLevelStr)
379        except ValueError:
380            theLevel = None
381
382        # Clean up the text property
383        theName = theName.replace('"', '`').replace("'", "`").replace("\r", "").replace("\n", "")
384
385        # Build the MindmapNotes object if notes are non-empty
386        notes_obj = MindmapNotes(text=theNotes) if theNotes else None
387
388        # Parse references:
389        # referencesRaw might look like "GUID1||GUID2||--||GUID3||GUID4||--||"
390        references = []
391        if referencesRaw:
392            rel_chunks = referencesRaw.split("||--||")
393            for chunk in rel_chunks:
394                chunk = chunk.strip()
395                if not chunk:
396                    continue
397                pair = chunk.split("||")
398                if len(pair) == 2:
399                    sLoc, eLoc = pair
400                    if sLoc == theGUID:  # If it matches the old pattern
401                        references.append(
402                            MindmapReference(direction=1, guid_1=sLoc, guid_2=eLoc)
403                        )
404                    else:
405                        # Or handle direction=2 or other logic if needed
406                        pass
407
408        # For now, links, icons, tags, image remain unimplemented => empty
409        links = []
410        icons = []
411        tags = []
412        image = None
413
414        return MindmapTopic(
415            guid=theGUID,
416            text=theName,
417            rtf=theTitle,
418            level=theLevel,
419            notes=notes_obj,
420            links=links,
421            image=image,
422            icons=icons,
423            tags=tags,
424            references=references,
425        )
426    
427    def get_topic_by_id(self, topic_id):
428        return topic_id
429
430    def get_selection(self):
431        """
432        Return a list of topic IDs in the current selection.
433        """
434        result = self._read("getSelection")
435        return result
436
437    def get_level_from_topic(self, topic_id):
438        if not topic_id:
439            return None
440        script = f'''
441            tell application "MindManager"
442                try
443                    set theTopic to first topic of document 1 whose id is "{topic_id}"
444                    return level of theTopic
445                on error
446                    return ""
447                end try
448            end tell
449        '''
450        level_str = _run_applescript(script)
451        return int(level_str) if level_str.isdigit() else None
452
453    def get_text_from_topic(self, topic_id):
454        if not topic_id:
455            return ""
456        script = f'''
457            tell application "MindManager"
458                try
459                    set theTopic to first topic of document 1 whose id is "{topic_id}"
460                    return name of theTopic
461                on error
462                    return ""
463                end try
464            end tell
465        '''
466        text = _run_applescript(script)
467        # Replace certain characters (as in original code)
468        text = text.replace('"', '`').replace("'", "`").replace("\r", "").replace("\n", "")
469        return text
470
471    def get_title_from_topic(self, topic_id):
472        if not topic_id:
473            return ""
474        script = f'''
475            tell application "MindManager"
476                try
477                    set theTopic to first topic of document 1 whose id is "{topic_id}"
478                    return title of theTopic
479                on error
480                    return ""
481                end try
482            end tell
483        '''
484        return _run_applescript(script)
485
486    def get_subtopics_from_topic(self, topic_id):
487        """
488        Return a list of subtopic IDs.
489        """
490        if not topic_id:
491            return []
492        script = f'''
493            tell application "MindManager"
494                try
495                    set theTopic to first topic of document 1 whose id is "{topic_id}"
496                    set subTs to subtopics of theTopic
497                    set output to ""
498                    repeat with t in subTs
499                        set output to output & (id of t) & linefeed
500                    end repeat
501                    return output
502                on error
503                    return ""
504                end try
505            end tell
506        '''
507        raw = _run_applescript(script)
508        return [x.strip() for x in raw.splitlines() if x.strip()]
509
510    def get_links_from_topic(self, topic_id) -> list[MindmapLink]:
511        return []
512
513    def get_image_from_topic(self, topic_id) -> MindmapImage:
514        return None
515
516    def get_icons_from_topic(self, topic_id) -> list[MindmapIcon]:
517        return []
518
519    def get_notes_from_topic(self, topic_id) -> MindmapNotes:
520        """
521        Return MindmapNotes or None.
522        """
523        if not topic_id:
524            return None
525        script = f'''
526            tell application "MindManager"
527                try
528                    set theTopic to first topic of document 1 whose id is "{topic_id}"
529                    return notes of theTopic
530                on error
531                    return ""
532                end try
533            end tell
534        '''
535        notes_text = _run_applescript(script)
536        if notes_text:
537            return MindmapNotes(text=notes_text)
538        return None
539
540    def get_tags_from_topic(self, topic_id) -> list[MindmapTag]:
541        return []
542
543    def get_references_from_topic(self, topic_id) -> list[MindmapReference]:
544        """
545        Return a list of MindmapReference objects for the given topic.
546        """
547        references = []
548        if not topic_id:
549            return references
550
551        script = f'''
552            tell application "MindManager"
553                try
554                    set theTopic to first topic of document 1 whose id is "{topic_id}"
555                    set rels to relationships of theTopic
556                    if (count of rels) = 0 then
557                        return ""
558                    end if
559                    set outList to ""
560                    repeat with r in rels
561                        set sLoc to id of (starting location of r)
562                        set eLoc to id of (ending location of r)
563                        set outList to outList & sLoc & "||" & eLoc & linefeed
564                    end repeat
565                    return outList
566                on error
567                    return ""
568                end try
569            end tell
570        '''
571        raw = _run_applescript(script)
572        for line in raw.splitlines():
573            parts = line.split("||")
574            if len(parts) == 2:
575                sLoc, eLoc = parts
576                if sLoc == topic_id:
577                    references.append(
578                        MindmapReference(
579                            direction=1,
580                            guid_1=sLoc,
581                            guid_2=eLoc
582                        )
583                    )
584        return references
585
586    def get_guid_from_topic(self, topic_id) -> str:
587        return topic_id if topic_id else ""
588
589    def add_subtopic_to_topic(self, topic_id, topic_text):
590        """
591        Create a new subtopic under `topic_id` with `topic_text`.
592        Return the new subtopic's ID or None on failure.
593        """
594        if not topic_id:
595            return None
596        safe_text = topic_text.replace('"', '\\"')
597        script = f'''
598            tell application "MindManager"
599                try
600                    set parentTopic to first topic of document 1 whose id is "{topic_id}"
601                    set newT to make new topic at end of subtopics of parentTopic with properties {{name:"{safe_text}"}}
602                    return id of newT
603                on error
604                    return ""
605                end try
606            end tell
607        '''
608        new_id = _run_applescript(script)
609        return new_id if new_id else None
610
611    def get_parent_from_topic(self, topic_id):
612        """
613        Return the parent's ID or None if there is no parent or the topic doesn't exist.
614        """
615        if not topic_id:
616            return None
617        script = f'''
618            tell application "MindManager"
619                try
620                    set theTopic to first topic of document 1 whose id is "{topic_id}"
621                    set p to parent of theTopic
622                    if p is not missing value then
623                        return id of p
624                    else
625                        return ""
626                    end if
627                on error
628                    return ""
629                end try
630            end tell
631        '''
632        result = _run_applescript(script)
633        return result if result else None
634
635    def set_text_to_topic(self, topic_id, topic_text):
636        """
637        Set the topic's text (equivalent to topic.name.set).
638        """
639        if not topic_id:
640            return
641        safe_text = topic_text.replace('"', '\\"')
642        script = f'''
643            tell application "MindManager"
644                try
645                    set theTopic to first topic of document 1 whose id is "{topic_id}"
646                    set name of theTopic to "{safe_text}"
647                end try
648            end tell
649        '''
650        _run_applescript(script)
651
652    def set_title_to_topic(self, topic_id, topic_rtf):
653        """
654        Set the topic's title (equivalent to topic.title.set).
655        """
656        if not topic_id:
657            return
658        safe_text = topic_rtf.replace('"', '\\"')
659        script = f'''
660            tell application "MindManager"
661                try
662                    set theTopic to first topic of document 1 whose id is "{topic_id}"
663                    set title of theTopic to "{safe_text}"
664                end try
665            end tell
666        '''
667        _run_applescript(script)
668
669    def add_tag_to_topic(self, topic_id, tag_text, topic_guid):
670        pass
671
672    def set_topic_from_mindmap_topic(self, topic_id, mindmap_topic, map_icons):
673        """
674        Updates the topic's text, RTF title, and notes from `mindmap_topic` 
675        via a single AppleScript call. 
676        Returns (refreshed_topic_id, original_topic_id).
677        """
678        if not topic_id and not mindmap_topic:
679            return None, None
680        
681        if not topic_id:
682            try:
683                self._write("writeTree", mindmap_topic)
684                return None, None
685
686            except Exception as e:
687                print(f"Error in set_topic_from_mindmap_topic: {e}")
688                return None, None
689        else:
690            try:
691                script_lines = []
692                script_lines.append('tell application "MindManager"')
693                script_lines.append('    try')
694                script_lines.append(f'        set theTopic to first topic of document 1 whose id is "{topic_id}"')
695                safe_text = (mindmap_topic.text or "").replace('"', '\\"')
696                script_lines.append(f'        set name of theTopic to "{safe_text}"')
697                if mindmap_topic.rtf:
698                    safe_rtf = mindmap_topic.rtf.replace('"', '\\"')
699                    script_lines.append(f'        set title of theTopic to "{safe_rtf}"')
700                if mindmap_topic.notes:
701                    safe_notes = (mindmap_topic.notes.text or "").replace('"', '\\"')
702                    script_lines.append(f'        set notes of theTopic to "{safe_notes}"')
703                script_lines.append('        return id of theTopic')
704                script_lines.append('    on error errMsg')
705                script_lines.append('        return ""')
706                script_lines.append('    end try')
707                script_lines.append('end tell')
708                full_script = "\n".join(script_lines)
709                refreshed_id = _run_applescript(full_script)
710                if not refreshed_id:
711                    return None, None
712                return refreshed_id, topic_id
713
714            except Exception as e:
715                print(f"Error in set_topic_from_mindmap_topic: {e}")
716                return None, None
717
718    def create_map_icons(self, map_icons):
719        pass
720
721    def create_tags(self, tags: list[str], DUPLICATED_TAG: str):
722        pass
723
724    def add_relationship(self, guid1, guid2, label=''):
725        if not guid1 or not guid2:
726            print("Error in add_relationship: One or both topic IDs missing.")
727            return
728        script = f'''
729            tell application "MindManager"
730                try
731                    set t1 to first topic of document 1 whose id is "{guid1}"
732                    set t2 to first topic of document 1 whose id is "{guid2}"
733                    if t1 is not missing value and t2 is not missing value then
734                        make new relationship with properties {{starting location:t1, ending location:t2}}
735                    end if
736                on error errMsg
737                    return ""
738                end try
739            end tell
740        '''
741        _run_applescript(script)
742
743    def add_topic_link(self, guid1, guid2, label=''):
744        pass
745
746    def add_document(self, max_topic_level):
747        """
748        Opens the correct template based on charttype and subtopic counts.
749        """
750        if not self.document_exists():
751            cnt_subtopics = 0
752        else:
753            script_count = '''
754                tell application "MindManager"
755                    set c to count of subtopics of central topic of document 1
756                    return c
757                end tell
758            '''
759            res = _run_applescript(script_count)
760            try:
761                cnt_subtopics = int(res)
762            except:
763                cnt_subtopics = 0
764
765        if self._charttype == "orgchart":
766            template_alias = self._orgchart_template
767        elif self._charttype == "radial":
768            template_alias = self._radial_template
769        else:
770            # "auto"
771            if max_topic_level > 2 and cnt_subtopics > 4:
772                template_alias = self._orgchart_template
773            else:
774                template_alias = self._radial_template
775
776        safe_path = template_alias.replace('"', '\\"')
777        script_open = f'''
778            tell application "MindManager"
779                open POSIX file "{safe_path}"
780            end tell
781        '''
782        _run_applescript(script_open)
783
784    def finalize(self, max_topic_level):
785        """
786        Balance the map, activate MindManager, optionally merge windows, and clean up.
787        """
788        if not self.document_exists():
789            return
790
791        # Balance map
792        script_balance = '''
793            tell application "MindManager"
794                try
795                    balance map of document 1
796                end try
797            end tell
798        '''
799        _run_applescript(script_balance)
800
801        # Activate MindManager
802        script_activate = '''
803            tell application "MindManager"
804                activate
805            end tell
806        '''
807        _run_applescript(script_activate)
808
809        # Optionally merge all windows
810        if self.MACOS_MERGE_ALL_WINDOWS:
811            self.merge_windows()
812
813        # No persistent object references to clear
814        pass