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