From 5c0a447773836344952a932f95330a8f236520d9 Mon Sep 17 00:00:00 2001 From: Marios-Nikolaidis Date: Sat, 4 Sep 2021 17:08:12 +0300 Subject: [PATCH 1/3] Minor changes to QtGui. Made possible to use ete GUI from other QApplications. Added leaf name color attribute --- ete3/ReadChanges.md | 34 ++++++++++++++++++++++++++++ ete3/coretype/tree.py | 8 +++++++ ete3/treeview/drawer.py | 8 ++++--- ete3/treeview/node_gui_actions.py | 10 +++++++++ ete3/treeview/qt4_gui.py | 37 +++++++++++++++++-------------- ete3/treeview/qt4_render.py | 7 +++++- 6 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 ete3/ReadChanges.md diff --git a/ete3/ReadChanges.md b/ete3/ReadChanges.md new file mode 100644 index 000000000..817815d8d --- /dev/null +++ b/ete3/ReadChanges.md @@ -0,0 +1,34 @@ +This file was created for an easy lookup of the changes made in the commit +Feel free to delete it afterwards + +# Treeview + +## Qt GUI changes + +1. Added the ability to ladderize a specific node (and it's children) both top to bottom and bottom to top +2. Created better dialog to save the tree view (as PDF file) +3. Added .newick and .nwk in the save newick dialog +4. Changed the QApplication widget to QMainWindow widget (explain below) + +#### references: + +1. top to bottom: node_gui_actions.py: lines 154, 270 - 272 + bottom to top: node_gui_actions.py: lines 155, 274 - 276 +2. qt4_gui.py: lines 386 - 394 & lines 401 - 409 +3. qt4_gui.py: lines 372, 374 +4. drawer.py: lines 67, 68, 95, 156, 157 + + When embedding the tree.show() function inside another GUI application (as I am trying to do for a project of mine) the following error occurs. + `QCoreApplication::exec: The event loop is already running` + since an application is getting called by another application and this is fatal for the main loop. + Exiting the first application will ***not*** close the second application, which will therefore consume system resources until manually killed. For this reason the QApplication widget can be changed with a QMainWindow widget, since the ete GUI uses only one primary window. As far as I have read the scripts there is no need for an event loop when displaying the window. + If you think that this change is not good feel free to discuss. + +## Leaf name color attribute + +1. Created function to add leaf text color +2. Fixed code to not add default (black) text face as leaf label if one is already specified. The user may want to add a custom color on leaf labels + +#### references: +1. tree.py: lines 2590 - 2596 +2. qt4_render.py: lines 263 - 268 diff --git a/ete3/coretype/tree.py b/ete3/coretype/tree.py index d0d3e93cd..6da8684c8 100644 --- a/ete3/coretype/tree.py +++ b/ete3/coretype/tree.py @@ -68,6 +68,7 @@ DEFAULT_DIST = 1.0 DEFAULT_SUPPORT = 1.0 DEFAULT_NAME = "" +DEFAULT_TEXT_COLOR = "black" class TreeError(Exception): """ @@ -2586,6 +2587,13 @@ def phonehome(self): from .. import _ph _ph.call() + def add_leaf_text_color(self, color=None): + # Added the extra function to use the is_leaf() bool + if self.is_leaf(): + if color == None: + color = "#000000" + self.add_feature("text_color",color) + def _translate_nodes(root, *nodes): name2node = dict([ [n, None] for n in nodes if type(n) is str]) if name2node: diff --git a/ete3/treeview/drawer.py b/ete3/treeview/drawer.py index c6785a43e..c43bde464 100644 --- a/ete3/treeview/drawer.py +++ b/ete3/treeview/drawer.py @@ -64,7 +64,8 @@ def init_scene(t, layout, ts): ts.layout_fn = layout if not _QApp: - _QApp = QApplication(["ETE"]) + # _QApp = QApplication(["ETE"]) + _QApp = QMainWindow() scene = _TreeScene() #ts._scale = None @@ -91,7 +92,7 @@ def show_tree(t, layout=None, tree_style=None, win_name=None): signal.signal(signal.SIGALRM, exit_gui) signal.alarm(GUI_TIMEOUT) - _QApp.exec_() + # _QApp.exec_() def render_tree(t, imgName, w=None, h=None, layout=None, tree_style = None, header=None, units="px", @@ -152,7 +153,8 @@ def get_img(t, w=None, h=None, layout=None, tree_style = None, header=None, units="px", dpi=90, return_format="%%return"): global _QApp if not _QApp: - _QApp = QApplication(["ETE"]) + # _QApp = QApplication(["ETE"]) + _QApp = QMainWindow() r = RenderThread(t, layout, tree_style, w, h, dpi, units, return_format) r.start() diff --git a/ete3/treeview/node_gui_actions.py b/ete3/treeview/node_gui_actions.py index 7de5a04e3..10f492afe 100644 --- a/ete3/treeview/node_gui_actions.py +++ b/ete3/treeview/node_gui_actions.py @@ -151,6 +151,8 @@ def mouseDoubleClickEvent(self,e): def showActionPopup(self): contextMenu = QMenu() + contextMenu.addAction( "Ladderize top to bottom", self.ladderize_top) + contextMenu.addAction( "Ladderize bottom to top", self.ladderize_bottom) contextMenu.addAction( "Set as outgroup", self.set_as_outgroup) contextMenu.addAction( "Copy partition", self.copy_partition) contextMenu.addAction( "Cut partition", self.cut_partition) @@ -264,6 +266,14 @@ def toggle_collapse(self): self.node.img_style["draw_descendants"] ^= True self.scene().GUI.redraw() + def ladderize_top(self): + self.node.ladderize(1) + self.scene().GUI.redraw() + + def ladderize_bottom(self): + self.node.ladderize(0) + self.scene().GUI.redraw() + def cut_partition(self): self.scene().view.buffer_node = self.node self.node.detach() diff --git a/ete3/treeview/qt4_gui.py b/ete3/treeview/qt4_gui.py index b33bacea1..ecc92b104 100644 --- a/ete3/treeview/qt4_gui.py +++ b/ete3/treeview/qt4_gui.py @@ -369,9 +369,9 @@ def on_actionOpen_triggered(self): @QtCore.pyqtSlot() def on_actionSave_newick_triggered(self): - fname = QFileDialog.getSaveFileName(self ,"Save File", + fname = QFileDialog.getSaveFileName(self ,"Save Newick File", "/home", - "Newick (*.nh *.nhx *.nw )") + "Newick (*.nh *.nhx *.nw *.nwk *.newick)") nw = self.scene.tree.write() try: OUT = open(fname,"w") @@ -383,27 +383,30 @@ def on_actionSave_newick_triggered(self): @QtCore.pyqtSlot() def on_actionRenderPDF_triggered(self): - F = QFileDialog(self) - if F.exec_(): - imgName = str(F.selectedFiles()[0]) - if not imgName.endswith(".pdf"): - imgName += ".pdf" - save(self.scene, imgName) - + file_filter = "PDF file (*.PDF)" + response = QFileDialog.getSaveFileName(parent=self, + caption = "Save PDF file", + filter = file_filter + ) + if response != "": + save(self.scene, response[0]) + else: + return @QtCore.pyqtSlot() def on_actionRender_selected_region_triggered(self): if not self.scene.selector.isVisible(): return QMessageBox.information(self, "!",\ "You must select a region first") - - F = QFileDialog(self) - if F.exec_(): - imgName = str(F.selectedFiles()[0]) - if not imgName.endswith(".pdf"): - imgName += ".pdf" - save(imgName, take_region=True) - + file_filter = "PDF file (*.PDF)" + response = QFileDialog.getSaveFileName(parent=self, + caption = "Save PDF file", + filter = file_filter + ) + if response != "": + save(self.scene, response[0]) + else: + return @QtCore.pyqtSlot() def on_actionPaste_newick_triggered(self): diff --git a/ete3/treeview/qt4_render.py b/ete3/treeview/qt4_render.py index 10f30aed5..d4444c433 100644 --- a/ete3/treeview/qt4_render.py +++ b/ete3/treeview/qt4_render.py @@ -260,7 +260,12 @@ def render(root_node, img, hide_root=False): faces.add_face_to_node(su_face, n, 0, position="branch-bottom") if _leaf(n) and n.name and img.show_leaf_name: - faces.add_face_to_node(na_face, n, 0, position="branch-right") + if hasattr(n, "_faces") and 0 in n._faces.__dict__["branch-right"]: + pass + # If the node already has a text face in the position branch right in column 0 + # the user already has already added a face in that position (node name with certain color) + else: + faces.add_face_to_node(na_face, n, 0, position="branch-right") if _leaf(n):# or len(n.img_style["_faces"]["aligned"]): virtual_leaves += 1 From 15c20a990b2f07bc73c304aeb858e41ab39d6923 Mon Sep 17 00:00:00 2001 From: Marios-Nikolaidis Date: Sat, 4 Sep 2021 17:18:53 +0300 Subject: [PATCH 2/3] Deleted unnecessary file --- ete3/ReadChanges.md | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 ete3/ReadChanges.md diff --git a/ete3/ReadChanges.md b/ete3/ReadChanges.md deleted file mode 100644 index 817815d8d..000000000 --- a/ete3/ReadChanges.md +++ /dev/null @@ -1,34 +0,0 @@ -This file was created for an easy lookup of the changes made in the commit -Feel free to delete it afterwards - -# Treeview - -## Qt GUI changes - -1. Added the ability to ladderize a specific node (and it's children) both top to bottom and bottom to top -2. Created better dialog to save the tree view (as PDF file) -3. Added .newick and .nwk in the save newick dialog -4. Changed the QApplication widget to QMainWindow widget (explain below) - -#### references: - -1. top to bottom: node_gui_actions.py: lines 154, 270 - 272 - bottom to top: node_gui_actions.py: lines 155, 274 - 276 -2. qt4_gui.py: lines 386 - 394 & lines 401 - 409 -3. qt4_gui.py: lines 372, 374 -4. drawer.py: lines 67, 68, 95, 156, 157 - - When embedding the tree.show() function inside another GUI application (as I am trying to do for a project of mine) the following error occurs. - `QCoreApplication::exec: The event loop is already running` - since an application is getting called by another application and this is fatal for the main loop. - Exiting the first application will ***not*** close the second application, which will therefore consume system resources until manually killed. For this reason the QApplication widget can be changed with a QMainWindow widget, since the ete GUI uses only one primary window. As far as I have read the scripts there is no need for an event loop when displaying the window. - If you think that this change is not good feel free to discuss. - -## Leaf name color attribute - -1. Created function to add leaf text color -2. Fixed code to not add default (black) text face as leaf label if one is already specified. The user may want to add a custom color on leaf labels - -#### references: -1. tree.py: lines 2590 - 2596 -2. qt4_render.py: lines 263 - 268 From 8d5ac51d36ef9918c01127f89775468ac3d2712c Mon Sep 17 00:00:00 2001 From: Marios-Nikolaidis Date: Mon, 6 Sep 2021 08:49:01 +0300 Subject: [PATCH 3/3] Added child_app kwarg to make the treeview GUI work both embedded and standalone --- ete3/coretype/tree.py | 5 +++-- ete3/treeview/drawer.py | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/ete3/coretype/tree.py b/ete3/coretype/tree.py index 6da8684c8..23bd7b5c3 100644 --- a/ete3/coretype/tree.py +++ b/ete3/coretype/tree.py @@ -1354,7 +1354,7 @@ def unroot(self, mode='legacy'): else: raise TreeError("Cannot unroot a tree with only two leaves") - def show(self, layout=None, tree_style=None, name="ETE"): + def show(self, layout=None, tree_style=None, name="ETE", child_app = False): """ Starts an interactive session to visualize current node structure using provided layout and TreeStyle. @@ -1362,8 +1362,9 @@ def show(self, layout=None, tree_style=None, name="ETE"): """ from ..treeview import drawer drawer.show_tree(self, layout=layout, - tree_style=tree_style, win_name=name) + tree_style=tree_style, win_name=name, child_app = child_app) + def render(self, file_name, layout=None, w=None, h=None, \ tree_style=None, units="px", dpi=90): """ diff --git a/ete3/treeview/drawer.py b/ete3/treeview/drawer.py index c43bde464..21c3d29a4 100644 --- a/ete3/treeview/drawer.py +++ b/ete3/treeview/drawer.py @@ -56,24 +56,25 @@ def exit_gui(a, b): _QApp.exit(0) -def init_scene(t, layout, ts): +def init_scene(t, layout, ts, child_app = False): global _QApp ts = init_tree_style(t, ts) if layout: ts.layout_fn = layout - if not _QApp: - # _QApp = QApplication(["ETE"]) + if not child_app: + _QApp = QApplication(["ETE"]) + else: _QApp = QMainWindow() scene = _TreeScene() #ts._scale = None return scene, ts -def show_tree(t, layout=None, tree_style=None, win_name=None): +def show_tree(t, layout=None, tree_style=None, win_name=None, child_app=None): """ Interactively shows a tree.""" - scene, img = init_scene(t, layout, tree_style) + scene, img = init_scene(t, layout, tree_style, child_app=child_app) tree_item, n2i, n2f = render(t, img) scene.init_values(t, img, n2i, n2f) @@ -92,7 +93,10 @@ def show_tree(t, layout=None, tree_style=None, win_name=None): signal.signal(signal.SIGALRM, exit_gui) signal.alarm(GUI_TIMEOUT) - # _QApp.exec_() + if not child_app: + _QApp.exec_() + else: + pass def render_tree(t, imgName, w=None, h=None, layout=None, tree_style = None, header=None, units="px", @@ -150,12 +154,14 @@ def run(self): def get_img(t, w=None, h=None, layout=None, tree_style = None, - header=None, units="px", dpi=90, return_format="%%return"): + header=None, units="px", dpi=90, return_format="%%return", child_app = False): global _QApp - if not _QApp: - # _QApp = QApplication(["ETE"]) + if not child_app and not _QApp: + _QApp = QApplication(["ETE"]) + else: _QApp = QMainWindow() + r = RenderThread(t, layout, tree_style, w, h, dpi, units, return_format) r.start() r.wait()