Beispiel #1
0
    def testDelete(self):
        tree = Tree()
        copy_q = copy(insert_queue)
        for word in copy_q:
            tree.insert(word)

        tree.delete(copy_q[-1])
        self.assertEqual(tree.search(copy_q[-1]), None)

        copy_q.pop()
        copy_q.sort()
        self.assertEqual(tree.inorder_traverse(), " ".join(copy_q))
Beispiel #2
0
class TestTree(unittest.TestCase):
    def setUp(self):
        # Creating the tree
        self.tree = Tree()
        self.tree.insertData(3)
        self.tree.insertData(1)
        self.tree.insertData(7)
        self.tree.insertData(5)

    def tearDown(self):
        self.tree = None

    def testInsertData(self):
        root = self.tree.root
        self.assertEqual(root.data, 3)
        self.assertEqual(root.lchild.data, 1)
        self.assertEqual(root.rchild.data, 7)
        self.assertEqual(root.rchild.lchild.data, 5)

    def testDelete(self):
        root = self.tree.root
        # Testing deletion of node with 2 children.
        self.tree.delete(root, 3)
        self.assertEqual(root.data, 1)
        # Testing deletion of node with 1 child.
        self.tree.delete(root, 7)
        self.assertEqual(root.rchild.data, 5)
        # Testing deletion of leaf node.
        self.tree.delete(root, 5)
        self.assertEqual(root.rchild, None)

    def testIsKeyPresent(self):
        root = self.tree.root
        isKeyPresent = self.tree.isKeyPresent(root, 3)
        self.assertTrue(isKeyPresent)
        isKeyPresent = self.tree.isKeyPresent(root, 9)
        self.assertFalse(isKeyPresent)

    def testLargest(self):
        root = self.tree.root
        largest = self.tree.largest(root)
        self.assertEqual(largest.data, 7)
Beispiel #3
0
class TestTree(unittest.TestCase):
  def setUp(self):
    # Creating the tree
    self.tree = Tree()
    self.tree.insertData(3)
    self.tree.insertData(1)
    self.tree.insertData(7)
    self.tree.insertData(5)

  def tearDown(self):
    self.tree = None

  def testInsertData(self):
    root = self.tree.root
    self.assertEqual(root.data, 3)
    self.assertEqual(root.lchild.data, 1)
    self.assertEqual(root.rchild.data, 7)
    self.assertEqual(root.rchild.lchild.data, 5)

  def testDelete(self):
    root = self.tree.root
    # Testing deletion of node with 2 children.
    self.tree.delete(root, 3)
    self.assertEqual(root.data, 1)
    # Testing deletion of node with 1 child.
    self.tree.delete(root, 7)
    self.assertEqual(root.rchild.data, 5)
    # Testing deletion of leaf node.
    self.tree.delete(root, 5)
    self.assertEqual(root.rchild, None)

  def testIsKeyPresent(self):
    root = self.tree.root
    isKeyPresent = self.tree.isKeyPresent(root, 3)
    self.assertTrue(isKeyPresent)
    isKeyPresent = self.tree.isKeyPresent(root, 9)
    self.assertFalse(isKeyPresent)

  def testLargest(self):
    root = self.tree.root
    largest = self.tree.largest(root)
    self.assertEqual(largest.data, 7)
Beispiel #4
0
tree.insertData(46)
tree.insertData(49)
tree.insertData(55)
tree.insertData(52)
tree.insertData(51)
tree.insertData(29)
tree.insertData(35)
tree.insertData(32)
tree.insertData(36)

# To test immutability. 
# The code below throws an error becuase the object is now immutable
#tree.newProp = "testing immutability"

# Inorder traversal of a tree
print("Inorder traversal:")
tree.inorderPrint(tree.root)

# Check if a particular key is present in a tree or not
key = 3
isKeyPresent = tree.isKeyPresent(tree.root, key)
print("Is Key Present: ", isKeyPresent)

# Deleting a few nodes from the tree
tree.delete(tree.root, 50)
tree.delete(tree.root, 36)
tree.delete(tree.root, 40)

# Inorder traversal after deleting the nodes
print("Inorder traversal:")
tree.inorderPrint(tree.root)
Beispiel #5
0
def main():
    start = time.time()

    # read in the data as a list[list[tconst, average_rating, num_votes]]
    data = parse_data()
    data.sort(key=lambda record: (record[1], record[0]))

    # init tree
    tree = Tree()

    # initialize data block
    data_id = Disk.get_next_free()
    data_block = Disk.read_block(data_id)
    set_data_block_header(data_block, data_id)

    # keep track of the number of data blocks
    num_data_blocks = 0

    # insert the data
    for i, record in enumerate(data):
        if i != 0 and i % 50000 == 0:
            print(f"{i} records inserted")
        record_bytes = convert_record_to_bytes(record)
        # insert into data block
        inserted_at = insert_record_bytes(data_block, record_bytes)
        if inserted_at == -1:
            num_data_blocks += 1
            data_id = Disk.get_next_free()
            data_block = Disk.read_block(data_id)
            set_data_block_header(data_block, data_id)
            inserted_at = insert_record_bytes(data_block, record_bytes)
            assert inserted_at != -1
        # write to disk for every record insertion
        Disk.write_block(data_id, data_block)
        # insert to B+ Tree (block_id, offset, key)
        tree.insert((record[1], record[0]), (data_id, inserted_at))

    end = time.time()
    print(f"Seconds for insertion: {end-start}")

    start = time.time()
    tree.save()
    end = time.time()
    print(f"Seconds for saving tree to disk: {end-start}")

    def get_ptr_key_sequence(node):
        keys_list = node.keys
        ptrs_list = node.get_child_ids()
        lastPtr = ptrs_list.pop()
        result = ""
        result += "| headers | "
        for ptrInd, ptrVal in enumerate(ptrs_list):
            result += f"{ptrVal} | "
            result += f"{keys_list[ptrInd]} | "
        result += f"{lastPtr} | \n"
        return result

    def generate_select_query_statistic(leaf_nodes_dict, non_leaf_nodes_dict,
                                        blocks_offsets_list, file_settings):
        unique_data_block_ids = set(
            block_id for block_id, _ in
            blocks_offsets_list)  # since pointers can point to same data block
        selected_records = [
            convert_bytes_to_record(
                read_record_bytes(Disk.read_block(block_id), offset))
            for block_id, offset in blocks_offsets
        ]

        # Index Nodes
        index_file = file_settings[0]
        data_file = file_settings[1]
        result_file = file_settings[2]

        # index nodes
        ind_file = open(index_file, "w")
        ind_file.write("The content of the non-leaf index nodes:\n")
        for ind, index_node in enumerate(Tracker.track_set["non-leaf"]):
            if index_node.parent:
                ind_file.write(
                    f"{ind}. node_id = {index_node.block_id} with parent_node_id = {index_node.parent.block_id}\n"
                )
            else:
                ind_file.write(
                    f"Root Node's node_id = {index_node.block_id}\n")
            ind_file.write(get_ptr_key_sequence(index_node))

        ind_file.write("\nThe content of the leaf index nodes:\n")
        count = 0
        for index_node in Tracker.track_set[
                "leaf"]:  # might just give first 5 in report
            count += 1
            ind_file.write(
                f"{count}. node_id = {index_node.block_id} with parent_block_id = {index_node.parent.block_id}\n"
            )
            ind_file.write(get_ptr_key_sequence(index_node))
        print(
            f"The number of index nodes the process accessed: {len(leaf_nodes_dict) + len(non_leaf_nodes_dict)} "
            f"({len(non_leaf_nodes_dict)} Non-leaf nodes, {len(leaf_nodes_dict)} leaf nodes)"
        )
        print(f'Content of index nodes accessed saved to "{index_file}"\n')

        # data blocks
        d_file = open(data_file, "w")
        d_file.write("The content of the data blocks:\n")
        for data_block_id in unique_data_block_ids:
            d_file.write(f"Records for data block with id {data_block_id}:\n")
            d_file.write("| ")
            d_file.write(
                f"{' | '.join('{:^27}'.format(str(record)) for record in read_all_records_from_data_block(Disk.read_block(data_block_id)))}"
            )
            d_file.write(" |\n")
        print(
            f"The number of data blocks the process accessed: {len(unique_data_block_ids)}"
        )
        print(f'Content of data blocks accessed saved to "{data_file}"\n')

        # tconst of movies
        tconst_records = [record[0] for record in selected_records]
        df = pd.DataFrame(tconst_records, columns=["tconst of movies"])
        df.to_csv(result_file)
        print(f"Result:")
        print(
            f'tconst of {len(selected_records)} movies saved to "{result_file}"\n'
        )

    # experiment 1
    print("Experiment 1: Storing the data on the disk...\n")
    block_count = num_data_blocks + 1
    node_count = tree.get_num_nodes()
    print(f"Total number of data blocks: {block_count}"
          )  # num_data_blocks were fully filled, last is partially filled
    print(f"Total number of index nodes: {node_count}")
    print(
        f"Total size of database: ({block_count} + {node_count}) * {BLOCK_SIZE}B = {(block_count + node_count)*BLOCK_SIZE}B\n"
    )

    # experiment 2
    print(
        "Experiment 2: Building a B+ tree on the attribute 'averageRating'...\n"
    )
    print(f"The parameter n of the B+ tree is: {tree.root.max_keys}")
    print(f"Total number of nodes in the B+ tree is: {tree.get_num_nodes()}")
    print(f"The height of the B+ tree is: {tree.get_height()}")
    print(f"The root node contents are: \n" f"node_id: {tree.root.block_id}")
    print(get_ptr_key_sequence(tree.root))

    print("The child node (of the root node) contents are:")
    count = 0
    for child in tree.root.pointers:
        count += 1
        print(f"{count}. node_id: {child.block_id}")
        print(get_ptr_key_sequence(child))

    # experiment 3
    print(
        "Experiment 3: Retrieving tconst of movies with averageRating == 8...\n"
    )
    file_settings = [
        f"{BLOCK_SIZE}B_experiment_3_index_nodes.txt",
        f"{BLOCK_SIZE}B_experiment_3_data_blocks.txt",
        f"{BLOCK_SIZE}B_experiment_3_tconst_result.csv"
    ]

    Tracker.reset_all()
    blocks_offsets = tree.search(8.0)
    generate_select_query_statistic(Tracker.track_set['leaf'],
                                    Tracker.track_set['non-leaf'],
                                    blocks_offsets, file_settings)

    selected_records = [
        convert_bytes_to_record(
            read_record_bytes(Disk.read_block(block_id), offset))
        for block_id, offset in blocks_offsets
    ]
    # the part below only for validation
    actual_records = []
    for record in data:
        if record[1] == 8.0:
            actual_records.append(record)
    assert sorted(selected_records) == sorted(actual_records)
    # tree.validate()

    # experiment 4
    print(
        "\nExperiment 4: Retrieving tconst of movies with 7 <= averageRating <= 9...\n"
    )
    file_settings = [
        f"{BLOCK_SIZE}B_experiment_4_index_nodes.txt",
        f"{BLOCK_SIZE}B_experiment_4_data_blocks.txt",
        f"{BLOCK_SIZE}B_experiment_4_tconst_result.csv"
    ]
    Tracker.reset_all()
    blocks_offsets = tree.search_range(7.0, 9.0)
    generate_select_query_statistic(Tracker.track_set['leaf'],
                                    Tracker.track_set['non-leaf'],
                                    blocks_offsets, file_settings)

    selected_records = [
        convert_bytes_to_record(
            read_record_bytes(Disk.read_block(block_id), offset))
        for block_id, offset in blocks_offsets
    ]
    # the part below only for validation
    actual_records = []
    for record in data:
        if 7.0 <= record[1] <= 9.0:
            actual_records.append(record)
    assert sorted(selected_records) == sorted(actual_records)
    # tree.validate()

    # experiment 5
    Tracker.reset_all()
    print(
        "Experiment 5: Deleting movies with averageRating == 7 and Updating B+ Tree...\n"
    )
    tree.delete(7.0)
    print(
        f"The number of times that a node is deleted: {Tracker.track_counts['merge']}"
    )
    print(f"Total number of nodes in the B+ tree is: {tree.get_num_nodes()}")
    print(f"The height of the B+ tree is: {tree.get_height()}")
    print(f"The root node contents are: \n" f"node_id: {tree.root.block_id}")
    print(get_ptr_key_sequence(tree.root))

    print("The child node (of the root node) contents are:")
    count = 0
    for child in tree.root.pointers:
        count += 1
        print(f"{count}. node_id: {child.block_id}")
        print(get_ptr_key_sequence(child))

    # the part below only for validation
    blocks_offsets = tree.search_range(None, None)
    records_remaining = [
        convert_bytes_to_record(
            read_record_bytes(Disk.read_block(block_id), offset))
        for block_id, offset in blocks_offsets
    ]
    actual_records_remaining = [record for record in data if record[1] != 7.0]
    assert sorted(records_remaining) == sorted(actual_records_remaining)
Beispiel #6
0
import subprocess
import sys

if __name__ == "__main__":
    tree = Tree(5)
    tree.add_value(3)
    tree.add_value(9)
    tree.add_value(10)
    tree.add_value(6)
    tree.add_value(7)


    tree.traverse()
    tree.rotate_to_root(7)

    tree.traverse()
    tree.rotate_to_root(7)
    tree.traverse()

    tree.add_value(2)
    tree.add_value(4)

    tree.add_value(8)
    tree.traverse()

    tree.rotate_to_root(4)
    tree.delete(4)
    tree.traverse()

    subprocess.call("ponysay Я СДЕЛАЛЪ", shell=True)
Beispiel #7
0
from tree import Tree
from node import Node

root_node = Node(42)
n_554 = Node(554)
n_55 = Node(34)
root = Tree(root_node)
root.insert(Node(4))
root.insert(Node(2))
root.insert(Node(5))
root.insert(Node(6))
root.insert(n_55)

root.insert(n_554)
root.insert(Node(72))
root.insert(Node(33))
root.delete(55)
# root.printTree()
root.printOrder()
Beispiel #8
0
class Editor(wx.Frame):
    def __init__(self, path=None, system=None):
        wx.Frame.__init__(self, parent=None, title="Treemendous")

        # Variables.
        self.tree = Tree()
        self.treectrl = None
        self._expanded = []
        if system:
            self.platform = system
        else:
            self.platform = platform.system()
        if self.platform != "Windows":
            msg = wx.MessageDialog(
                self,
                _(
                    # Translators: A message shown when a user launches Treemendous on an unsupported OS.
                    "Support for your operating system ({platform}) has not been fully verified by the Treemendous developers. If you proceed, a baseline level of functionality and accessibility may be present, but the user experience may not be complete or native to your system. In particular, tree nodes may appear in the wrong order, and some keyboard commands may not function at all or as intended. If the usual commands for opening the context menu on a tree node do not work, try pressing enter or double-clicking the selected node with the mouse. If the arrow keys do not expand/collapse nodes, try plus/minus."
                ).format(platform=self.platform),
                # Translators: The title of a message box.
                _("OS not supported"),
                wx.OK | wx.OK_DEFAULT | wx.CANCEL | wx.ICON_WARNING,
            )
            if msg.ShowModal() != wx.ID_OK:
                raise RuntimeError("OS not supported")

        self.AutoUpdate()

        # Setting up menubar.
        menubar = wx.MenuBar()

        file = wx.Menu()
        new = wx.MenuItem(
            file,
            wx.ID_NEW,
            # Translators: An item in the file menu.
            _("&New\tCtrl+n"),
            # Translators: Help text for "new" in the file menu.
            _("Creates a new blank Treemendous instance."),
        )
        file.Append(new)

        open = wx.MenuItem(
            file,
            wx.ID_OPEN,
            # Translators: An item in the file menu.
            _("&Open\tCtrl+O"),
            # Translators: Help text for "open" in the file menu.
            _("Open an existing tree."),
        )
        file.Append(open)
        file.AppendSeparator()

        save = wx.MenuItem(
            file,
            wx.ID_SAVE,
            # Translators: An item in the file menu.
            _("&Save\tCtrl+S"),
            # Translators: Help text for "save" in the file menu.
            _("Save this tree to disk."),
        )
        file.Append(save)

        saveas = wx.MenuItem(
            file,
            wx.ID_SAVEAS,
            # Translators: An item in the file menu.
            _("Save &as...\tShift+Ctrl+S"),
            # Translators: Help text for "save as" in the file menu.
            _("Save this tree to a different location."),
        )
        file.Append(saveas)
        file.AppendSeparator()

        quit = wx.MenuItem(
            file,
            wx.ID_EXIT,
            # Translators: An item in the file menu.
            _("&Quit\tCtrl+Q"),
            # Translators: Help text for "quit" in the file menu.
            _("Exit Treemendous."),
        )
        file.Append(quit)

        edit = wx.Menu()
        copy = wx.MenuItem(
            edit,
            wx.ID_COPY,
            # Translators: An item in the edit menu.
            _("Copy\tCTRL+c"),
            # Translators: Help text for "copy" in the edit menu.
            "Copy the currently selected node to the pasteboard.",
        )
        paste = wx.MenuItem(
            edit,
            wx.ID_PASTE,
            # Translators: An item in the edit menu.
            _("Paste\tCTRL+v"),
            _(
                # Translators: Help text for "paste" in the edit menu.
                "Place the contents of the pasteboard in the tree at the specified position."
            ),
        )
        edit.Append(copy)
        edit.Append(paste)

        view = wx.Menu()
        self.viewVisualMenuItem = wx.MenuItem(
            view,
            wx.ID_ANY,
            # Translators: An item in the "view" menu.
            _("&Visual"),
            _(
                # Translators: Help text for "visual" in the view menu.
                "Show a graphical representation of this tree."),
        )
        view.Append(self.viewVisualMenuItem)
        self.NotesCheckBox = wx.MenuItem(
            view,
            wx.ID_ANY,
            # Translators: An item in the "view" menu that toggles the display of the notes window.
            # The notes window allows users to enter freeform text along with their tree.
            _("&Notes"),
            _(
                # Translators: Help text for "notes" in the view menu.
                "Show or hide the notes window, which allows entry of freeform text to be shown along with the tree."
            ),
            kind=wx.ITEM_CHECK,
        )
        view.Append(self.NotesCheckBox)
        self.qtreeMenuItem = wx.MenuItem(
            view,
            wx.ID_ANY,
            # Translators: An item in the "view" menu.
            _("La&TeX (Qtree)"),
            _(
                # Translators: Help text for "LaTeX" in the view menu.
                "Show this tree as source code suitable for pasting into a LaTeX document. Requires that the qtree package be included in the document preamble."
            ),
        )
        view.Append(self.qtreeMenuItem)

        help = wx.Menu()
        about = wx.MenuItem(
            help,
            wx.ID_ABOUT,
            # Translators: An item in the help menu.
            _("&About\tF1"),
            # Translators: Help text for the "about" option in the help menu. Please indicate that this dialog is always in English.
            _("View version and licence."),
        )
        help.Append(about)

        menubar.Append(
            file,
            # Translators: The name of a menu in the menu bar.
            _("&File"),
        )
        menubar.Append(
            edit,
            # Translators: The name of a menu in the menu bar.
            _("&Edit"),
        )
        menubar.Append(
            view,
            # Translators: The name of a menu in the menu bar.
            _("&View"),
        )
        menubar.Append(
            help,
            # Translators: The name of a menu in the menu bar.
            _("&Help"),
        )

        self.SetMenuBar(menubar)

        self.panel = wx.Panel(self)

        notesSizer = wx.BoxSizer(wx.VERTICAL)
        self.notesLbl = wx.StaticText(
            self.panel,
            # Translators: The label for the notes window.
            label=_("&Notes:"),
        )
        notesSizer.Add(self.notesLbl)
        self.notesField = wx.TextCtrl(self.panel,
                                      style=wx.TE_MULTILINE | wx.TE_RICH2,
                                      value=self.tree.notes)
        notesSizer.Add(self.notesField, flag=wx.EXPAND | wx.TOP | wx.BOTTOM)

        self.notesField.Bind(wx.EVT_TEXT, self.OnNotesChanged)

        self.addNodeButton = wx.Button(
            self.panel,
            # Translators: The label of the add node button in the main window.
            label=_("&Add..."),
        )
        self.addNodeButton.Bind(wx.EVT_BUTTON, self.OnAddNode)

        self.Bind(wx.EVT_MENU, self.NewInstance, id=wx.ID_NEW)
        self.Bind(wx.EVT_MENU, self.OnOpenFile, id=wx.ID_OPEN)
        self.Bind(wx.EVT_MENU, self.OnSaveFile, id=wx.ID_SAVE)
        self.Bind(wx.EVT_MENU, self.OnSaveAsFile, id=wx.ID_SAVEAS)
        self.Bind(wx.EVT_MENU, self.QuitApplication, id=wx.ID_EXIT)
        self.Bind(wx.EVT_MENU, self.OnCopy, id=wx.ID_COPY)
        self.Bind(wx.EVT_MENU, self.OnPaste, id=wx.ID_PASTE)
        self.Bind(wx.EVT_MENU, self.OnEditNode, id=wx.ID_EDIT)
        self.Bind(wx.EVT_MENU, self.OnDeleteNode, id=wx.ID_DELETE)
        self.Bind(wx.EVT_MENU, self.OnViewVisual, self.viewVisualMenuItem)
        self.Bind(wx.EVT_MENU, self.OnToggleNotes, self.NotesCheckBox)
        self.Bind(wx.EVT_MENU, self.OnQtree, self.qtreeMenuItem)
        self.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT)
        self.Bind(wx.EVT_CLOSE, self.QuitApplication)

        fgs = wx.FlexGridSizer(3, 1, 5, 5)
        self.InitTree()
        fgs.Add(self.treectrl.widget, wx.ID_ANY, wx.EXPAND | wx.ALL, border=3)
        fgs.Add(
            notesSizer,
            wx.ID_ANY,
            flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
            border=3,
        )
        fgs.Add(self.addNodeButton, flag=wx.LEFT | wx.RIGHT, border=3)

        fgs.AddGrowableRow(0, 1)
        fgs.AddGrowableCol(0, 1)

        self.panel.SetSizer(fgs)

        self.StatusBar()

        self.Centre()

        if path is not None:
            self.OpenTree(path)

        self.RenderTree()

        notesenabled = bool(self.tree.notes)
        self.EnableNotes(notesenabled)

        self.Show()

    def InitTree(self):
        ctrl = WinTreeCTRL if self.platform == "Windows" else MacTreeCTRL
        self.treectrl = ctrl(self.panel, wx.ID_ANY, wx.DefaultPosition,
                             wx.DefaultSize)
        self.treectrl.BindEvent(TreeEvent.ITEM_SELECTED,
                                self.OnSelectionChanged)
        self.treectrl.BindEvent(TreeEvent.ITEM_EXPANDED, self.OnExpand)
        self.treectrl.BindEvent(TreeEvent.ITEM_COLLAPSED, self.OnCollapse)
        self.treectrl.BindEvent(TreeEvent.CONTEXT_MENU, self.OnNodeContextMenu)
        self.treectrl.BindEvent(TreeEvent.KEY_DOWN, self.OnTreeKeyDown)

    def RenderTree(self):
        sel = self.tree.selection
        root = None
        toExpand = []

        def _initializeLevel(guiRoot, treeRoot):
            r = self.treectrl.AddChild(guiRoot, treeRoot, treeRoot
                                       in self._expanded)
            if treeRoot == sel:
                self.treectrl.Select(r)
            for c in treeRoot.children:
                _initializeLevel(r, c)

        self.UpdateName()

        self.viewVisualMenuItem.Enable(not self.tree.is_empty)
        self.qtreeMenuItem.Enable(not self.tree.is_empty)

        if not self.treectrl:
            self.InitTree()
        else:
            self.treectrl.DeleteAll()
        if not self.tree.is_empty:
            root = self.treectrl.AddRoot(self.tree.root, self.tree.root
                                         in self._expanded)
            if sel is None or sel == self.tree.root:
                self.treectrl.Select(root)
            for c in self.tree.root.children:
                _initializeLevel(root, c)
        self.treectrl.widget.SetFocus()

    def UpdateName(self):
        title = "Treemendous"
        if self.tree.last_path:
            name = os.path.splitext(os.path.basename(self.tree.last_path))[0]
            title = f"{name} – {title}"
        if self.tree.dirty:
            title = f"*{title}"
        self.SetTitle(title)

    def AutoUpdate(self):
        try:
            req = urllib.request.Request(
                AUTOUPDATE_ENDPOINT,
                headers={
                    "User-Agent":
                    f"Mozilla/5.0 (compatible; python-Treemendous/{__version__}; +https://github.com/codeofdusk/treemendous)"
                },
            )
            resp = json.loads(urllib.request.urlopen(req, timeout=10).read())
        except (URLError, JSONDecodeError) as e:
            # Translators: Part of a message printed to the command line when the update check could not be completed.
            UPDATE_FAIL = _("Error while checking for updates:")
            print(f"{UPDATE_FAIL} {e}")
            return
        if AUTOUPDATE_SCHEMA_VERSION < resp.get("schema_version", maxsize):
            return self.UpdateAvailable(required=True)
        my_version = packaging.version.parse(__version__)
        latest_version = packaging.version.parse(resp["latest_version"])
        minimum_version = packaging.version.parse(resp["minimum_version"])
        if my_version < minimum_version:
            return self.UpdateAvailable(version=resp["latest_version"],
                                        page=resp["release_page"],
                                        required=True)
        elif my_version < latest_version:
            return self.UpdateAvailable(
                version=resp["latest_version"],
                page=resp["release_page"],
                required=False,
            )

    def NewInstance(self, event):
        editor = Editor(system=self.platform)
        editor.Centre()
        editor.Show()

    def OnOpenFile(self, event):
        file_name = os.path.basename(self.tree.last_path)

        if self.tree.dirty:
            dlg = wx.MessageDialog(
                self,
                # Translators: The text of a prompt asking if the user wants to save unsaved changes (for instance, when closing the program or opening a new tree over top of the current).
                _("Save changes ?"),
                "Treemendous",
                wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION,
            )

            val = dlg.ShowModal()
            if val == wx.ID_YES:
                self.OnSaveFile(event)
                self.DoOpenFile()
            elif val == wx.ID_CANCEL:
                dlg.Destroy()
            else:
                self.DoOpenFile()
        else:
            self.DoOpenFile()

    def OpenTree(self, path):
        try:
            self.tree = Tree(path)
            self._expanded = []
            self.EnableNotes(bool(self.tree.notes))
        except (IncompatibleFormatError, IOError) as e:
            dlg = wx.MessageDialog(
                self,
                str(e),
                # Translators: The title of an error dialog shown when a Treemendous file could not be opened.
                _("Error"),
                wx.ICON_ERROR,
            )
            dlg.ShowModal()

    def DoOpenFile(self):
        open_dlg = wx.FileDialog(
            self,
            # Translators: The title of the open file dialog.
            message=_("Choose tree"),
            defaultDir=os.getcwd(),
            defaultFile="",
            wildcard=WILDCARD,
            style=wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST
            | wx.FD_PREVIEW,
        )

        if open_dlg.ShowModal() == wx.ID_OK:
            path = open_dlg.GetPath()
            self.OpenTree(path)
            self.RenderTree()
            self.statusbar.SetStatusText("", 1)
        open_dlg.Destroy()

    def OnSaveFile(self, event):
        try:
            self.tree.save()
            self.statusbar.SetStatusText("", 1)
            self.UpdateName()
        except IOError as error:
            dlg = wx.MessageDialog(self, "Error saving file\n" + str(error))
            dlg.ShowModal()
        except SaveError:
            self.OnSaveAsFile(event)

    def OnSaveAsFile(self, event):
        save_dlg = wx.FileDialog(
            self,
            # Translators: The title of the "save tree as" dialog.
            message=_("Save tree as ..."),
            defaultDir=os.getcwd(),
            defaultFile="",
            wildcard=SAVE_WILDCARD,
            style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,
        )
        save_dlg.SetFilterIndex(0)

        if save_dlg.ShowModal() == wx.ID_OK:
            path = save_dlg.GetPath()

            if path.endswith(".png"):
                msg = wx.MessageDialog(
                    self,
                    _(
                        # Translators: A message shown when a user tries to save an image.
                        "Image files are inaccessible to screen reader users. To ensure accessibility, please provide an additional accessible format, such as a textual discription, Treemendous file, or Graphviz source code whereever you distribute this image."
                    ),
                    # Translators: The title of a message box.
                    _("Accessibility warning"),
                    wx.OK | wx.OK_DEFAULT | wx.CANCEL | wx.ICON_WARNING,
                )
                if msg.ShowModal() != wx.ID_OK:
                    return

            try:
                self.tree.save(path=path)
                self.statusbar.SetStatusText(self.tree.last_path + " Saved", 0)
                self.statusbar.SetStatusText("", 1)
                self.UpdateName()
            except GraphvizNotFound:
                self.GetGraphviz()
            except IOError as error:
                # Translators: Text displayed in a message box before the OS error message when a file could not be saved.
                HEADER = _("Error saving file")
                dlg = wx.MessageDialog(self, f"{HEADER}\n{error}")
                dlg.ShowModal()
        save_dlg.Destroy()

    def QuitApplication(self, event):
        if self.tree.dirty:
            dlg = wx.MessageDialog(
                self,
                # Translators: The text of a prompt asking if the user wants to save unsaved changes (for instance, when closing the program or opening a new tree over top of the current).
                _("Save changes ?"),
                "Treemendous",
                wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION,
            )
            val = dlg.ShowModal()
            if val == wx.ID_YES:
                self.OnSaveFile(event)
                if not self.tree.dirty:
                    wx.Exit()
            elif val == wx.ID_CANCEL:
                dlg.Destroy()
            else:
                self.Destroy()
        else:
            self.Destroy()

    def OnSelectionChanged(self, event):
        self.tree.selection = self.treectrl.GetNodeFromItem(event.GetItem())

    def OnExpand(self, event):
        itm = event.GetItem()
        if itm.IsOk:
            self._expanded.append(self.treectrl.GetNodeFromItem(itm))

    def OnCollapse(self, event):
        itm = event.GetItem()
        if itm.IsOk:
            self._expanded.remove(self.treectrl.GetNodeFromItem(itm))
            self.treectrl.CollapseChildren(itm)

    def OnNotesChanged(self, event):
        self.tree.notes = self.notesField.GetValue()
        self.UpdateName()

    def OnToggleNotes(self, event):
        notesenabled = not self.notesField.Shown
        self.EnableNotes(notesenabled)

    def EnableNotes(self, notesenabled):
        # Changing the value of the notes field causes self.notes to be updated and the dirty flag to be set.
        # Unbind the event handler temporarily when  refreshing the field to avoid this.
        self.notesField.Unbind(wx.EVT_TEXT)
        self.notesField.SetValue(self.tree.notes)
        self.notesField.Bind(wx.EVT_TEXT, self.OnNotesChanged)
        self.notesLbl.Show(notesenabled)
        self.notesField.Show(notesenabled)
        self.NotesCheckBox.Check(notesenabled)
        self.panel.GetSizer().Layout()  # Update control sizing after show/hide

    def OnAddNode(self, event):
        if not self.tree.is_empty:
            self.PopupMenu(AddNodeMenu(self))
        else:
            self.DoAddChild()

    def DoAddChild(self):
        title = (
            # Translators: The name of the dialog for adding the first node to a tree.
            _("Add root") if self.tree.is_empty
            # Translators: The name of the dialog for adding a child (contained item) to a node labelled {label}.
            else _("Add child of {label}").format(
                label=self.tree.selection.label))
        dlg = EditNodeDialog(title=title)
        if dlg.ShowModal() == wx.ID_OK:
            self.tree.add(Location.CHILD, dlg.label.GetValue(),
                          dlg.value.GetValue())
            self.RenderTree()
        dlg.Destroy()

    def DoAddParent(self):
        # Translators: The name of the dialog for adding a parent (containg item) to a node labelled {label}.
        title = _("Add parent of {label}").format(
            label=self.tree.selection.label)
        dlg = EditNodeDialog(title=title)
        if dlg.ShowModal() == wx.ID_OK:
            self.tree.add(Location.PARENT, dlg.label.GetValue(),
                          dlg.value.GetValue())
            self.RenderTree()
        dlg.Destroy()

    def DoAddSibling(self):
        # Translators: The name of the dialog for adding a sibling (item on same level) to a node labelled {label}.
        title = _("Add sibling of {label}").format(
            label=self.tree.selection.label)
        dlg = EditNodeDialog(title=title)
        if dlg.ShowModal() == wx.ID_OK:
            self.tree.add(Location.SIBLING, dlg.label.GetValue(),
                          dlg.value.GetValue())
            self.RenderTree()
        dlg.Destroy()

    def OnCopy(self, event):
        self.tree.copy()

    def OnPaste(self, event):
        if not self.tree.is_empty:
            self.PopupMenu(PasteDestMenu(self, event))
        else:
            self.DoPaste(Location.CHILD, event)

    def DoPaste(self, location, event):
        try:
            self.tree.paste(location)
            self.RenderTree()
        except SelectionError:  # Raised when the pasteboard is empty
            event.Skip()

    def PasteChild(self, event):
        self.DoPaste(Location.CHILD, event)

    def PasteParent(self, event):
        self.DoPaste(Location.PARENT, event)

    def PasteSibling(self, event):
        self.DoPaste(Location.SIBLING, event)

    def OnNodeContextMenu(self, event):
        if self.tree.is_empty:
            return event.Skip()
        self.PopupMenu(NodeContextMenu(self))

    def OnTreeKeyDown(self, event):
        keycode = event.GetKeyCode()
        if event.AltDown():
            if keycode == wx.WXK_UP:
                return self.OnMoveUp(event)
            elif keycode == wx.WXK_DOWN:
                return self.OnMoveDown(event)
        if keycode == wx.WXK_F2:
            return self.OnEditNode(event)
        elif keycode == wx.WXK_DELETE:
            return self.OnDeleteNode(event)
        else:
            return event.Skip()

    def OnEditNode(self, event):
        dlg = EditNodeDialog(
            # Translators: The name of the dialog for editing a node labelled {label}.
            title=_("Editing {label}").format(label=self.tree.selection.label),
            label=self.tree.selection.label,
            value=self.tree.selection.value,
        )
        if dlg.ShowModal() == wx.ID_OK:
            self.tree.edit(label=dlg.label.GetValue(),
                           value=dlg.value.GetValue())
            self.RenderTree()
        dlg.Destroy()

    def OnDeleteNode(self, event):
        # Translators: A confirmation message asking if the user wants to delete this node. Options are OK and cancel.
        LEAF_MSG = _("Are you sure that you want to delete this node?")
        NONLEAF_MSG = _(
            # Translators: A confirmation message asking if the user wants to delete this node and all of its descendants (children, grandchildren, etc.). Options are OK and cancel.
            "Are you sure that you want to delete this node and all descendants?"
        )
        dlg = wx.MessageDialog(
            self,
            LEAF_MSG if not self.tree.selection.children else NONLEAF_MSG,
            # Translators: Title of a message dialog confirming deletion of the node labelled {label}.
            _("Delete {label}").format(label=self.tree.selection.label),
            wx.OK | wx.OK_DEFAULT | wx.CANCEL | wx.ICON_WARNING,
        )

        val = dlg.ShowModal()
        if val == wx.ID_OK:
            self.tree.delete()
            self.RenderTree()

    def OnMoveUp(self, event):
        self.tree.move_up()
        self.RenderTree()

    def OnMoveDown(self, event):
        self.tree.move_down()
        self.RenderTree()

    def StatusBar(self):
        self.statusbar = self.CreateStatusBar()
        self.statusbar.SetFieldsCount(3)
        self.statusbar.SetStatusWidths([-5, -2, -1])

    def GetGraphviz(self):
        dlg = wx.MessageDialog(
            self,
            _(
                # Translators: Text of a message shown when the user tries to use a function that requires Graphviz but doesn't have it installed.
                "Treemendous requires that Graphviz is installed to perform this action, but it could not be found. If you proceed, the Graphviz website will be opened in your web browser so that you can download and install it. During installation, if prompted, please select to have Graphviz added to the system path. You may need to restart Treemendous after installation."
            ),
            # Translators: The title of a message box.
            _("Graphviz required"),
            wx.OK | wx.OK_DEFAULT | wx.CANCEL | wx.ICON_QUESTION,
        )

        val = dlg.ShowModal()
        if val == wx.ID_OK:
            webbrowser.open(GRAPHVIZ_DOWNLOAD_URL)

    def UpdateAvailable(self, version=None, page=None, required=False):
        if required:
            flags = wx.OK | wx.OK_DEFAULT | wx.ICON_ERROR
            # Translators: The title of a message box shown when a Treemendous update must be downloaded.
            title = _("Update required")
            if version:
                # Translators: Part of a message shown when a Treemendous update is required.
                body = _("An update to Treemendous {version} is required."
                         ).format(version=version)
            else:
                # Translators: Part of a message shown when a Treemendous update is required.
                body = _("A Treemendous update is required.")
            if page:
                # Translators: Part of a message shown when a Treemendous update is required.
                footer = _(
                    "The release page will be opened in your web browser so that you can download and install the update."
                )
            else:
                # Translators: Part of a message shown when a Treemendous update is required.
                footer = _(
                    "This update must be downloaded and installed manually.")
        else:
            flags = wx.OK | wx.OK_DEFAULT | wx.CANCEL | wx.ICON_QUESTION
            # Translators: The title of a message box shown when a Treemendous update is available for download, but not required.
            title = _("Update available")
            if version:
                # Translators: Part of a message shown when a Treemendous update is available, but not required.
                body = _("An update to Treemendous {version} is available."
                         ).format(version=version)
            else:
                # Translators: Part of a message shown when a Treemendous update is available, but not required.
                body = _("A Treemendous update is available.")
            if page:
                # Translators: Part of a message shown when a Treemendous update is available, but not required.
                footer = _(
                    "If you proceed, the release page will be opened in your web browser so that you can download and install the update."
                )
            else:
                # Translators: Part of a message shown when a Treemendous update is available, but not required.
                footer = _("Please manually download and install this update.")
        dlg = wx.MessageDialog(self, f"{body}\n{footer}", title, flags)

        val = dlg.ShowModal()
        if val == wx.ID_OK:
            if page:
                webbrowser.open(page)
            raise RuntimeError("Update requested, exiting.")

    def OnViewVisual(self, event):
        try:
            path = self.tree.graphviz(dpi=200)
        except GraphvizNotFound:
            self.GetGraphviz()
        else:
            dlg = VisualViewDialog(path, self.platform)
            dlg.ShowModal()
            dlg.Destroy()
            os.remove(path)

    def OnQtree(self, event):
        dlg = ReadOnlyViewDialog(
            # Translators: The title of a dialog displaying LaTeX source code for the currently opened tree.
            _("LaTeX source"),
            self.tree.qtree(),
        )
        dlg.ShowModal()
        dlg.Destroy()

    def OnAbout(self, event):
        dlg = wx.MessageDialog(
            self,
            (f"Treemendous {__version__}\n"
             "Copyright 2021 Bill Dengler and open-source contributors\n"
             "Licensed under the Mozilla Public License, v. 2.0: https://mozilla.org/MPL/2.0/"
             ),
            "Treemendous",
            wx.OK | wx.ICON_INFORMATION,
        )
        dlg.ShowModal()
        dlg.Destroy()
Beispiel #9
0
        ipt = which(tree.node_dict[node][0], children_names, 0, 0,
                    2)  #get input

        if ipt == 'q':  #quit
            break

        elif ipt == 'n':  #new
            pass

        elif ipt == 'e':  #edit
            pass

        elif ipt == 'd':  #delete
            if parent != -1:  #-1 as parent is the root node, shouldn't be deleted
                tree.delete(node)
                node = parent

        elif ipt == -1:  #go back
            if parent != -1:
                node = parent

        else:
            if children != []:  #go forward to selected node
                node = children[ipt]

        win.clear()
        win.refresh()

    curses.nocbreak()
    win.keypad(False)