Show More
@@ -0,0 +1,179 b'' | |||
|
1 | """Magic Helper - dockable widget showing magic commands for the MainWindow | |
|
2 | ||
|
3 | ||
|
4 | Authors: | |
|
5 | ||
|
6 | * Dimitry Kloper | |
|
7 | ||
|
8 | """ | |
|
9 | ||
|
10 | #----------------------------------------------------------------------------- | |
|
11 | # Imports | |
|
12 | #----------------------------------------------------------------------------- | |
|
13 | ||
|
14 | # stdlib imports | |
|
15 | import json | |
|
16 | import re | |
|
17 | import sys | |
|
18 | ||
|
19 | # System library imports | |
|
20 | from IPython.external.qt import QtGui,QtCore | |
|
21 | ||
|
22 | from IPython.core.magic import magic_escapes | |
|
23 | ||
|
24 | class MagicHelper(QtGui.QDockWidget): | |
|
25 | ||
|
26 | pasteRequested = QtCore.pyqtSignal(str, name = 'pasteRequested') | |
|
27 | runRequested = QtCore.pyqtSignal(str, name = 'runRequested') | |
|
28 | ||
|
29 | #--------------------------------------------------------------------------- | |
|
30 | # 'object' interface | |
|
31 | #--------------------------------------------------------------------------- | |
|
32 | ||
|
33 | def __init__(self, name, parent): | |
|
34 | ||
|
35 | super(MagicHelper, self).__init__(name, parent) | |
|
36 | ||
|
37 | # this is a hack. The main_window reference will be used for | |
|
38 | # explicit interface to kernel that must be hidden by signal/slot | |
|
39 | # mechanism in the future | |
|
40 | self.main_window = parent | |
|
41 | ||
|
42 | self.data = None | |
|
43 | ||
|
44 | class MinListWidget(QtGui.QListWidget): | |
|
45 | def sizeHint(self): | |
|
46 | s = QtCore.QSize() | |
|
47 | s.setHeight(super(MinListWidget,self).sizeHint().height()) | |
|
48 | s.setWidth(self.sizeHintForColumn(0)) | |
|
49 | return s | |
|
50 | ||
|
51 | self.frame = QtGui.QFrame() | |
|
52 | self.search_label = QtGui.QLabel("Search:") | |
|
53 | self.search_line = QtGui.QLineEdit() | |
|
54 | self.search_class = QtGui.QComboBox() | |
|
55 | self.search_list = MinListWidget() | |
|
56 | self.paste_button = QtGui.QPushButton("Paste") | |
|
57 | self.run_button = QtGui.QPushButton("Run") | |
|
58 | ||
|
59 | main_layout = QtGui.QVBoxLayout() | |
|
60 | search_layout = QtGui.QHBoxLayout() | |
|
61 | search_layout.addWidget(self.search_label) | |
|
62 | search_layout.addWidget(self.search_line, 10) | |
|
63 | main_layout.addLayout(search_layout) | |
|
64 | main_layout.addWidget(self.search_class) | |
|
65 | main_layout.addWidget(self.search_list, 10) | |
|
66 | action_layout = QtGui.QHBoxLayout() | |
|
67 | action_layout.addWidget(self.paste_button) | |
|
68 | action_layout.addWidget(self.run_button) | |
|
69 | main_layout.addLayout(action_layout) | |
|
70 | ||
|
71 | self.frame.setLayout(main_layout) | |
|
72 | self.setWidget(self.frame) | |
|
73 | ||
|
74 | self.visibilityChanged[bool].connect( self.update_magic_helper ) | |
|
75 | self.search_class.activated[int].connect( | |
|
76 | self.class_selected | |
|
77 | ) | |
|
78 | self.search_line.textChanged[str].connect( | |
|
79 | self.search_changed | |
|
80 | ) | |
|
81 | self.search_list.itemDoubleClicked[QtGui.QListWidgetItem].connect( | |
|
82 | self.paste_requested | |
|
83 | ) | |
|
84 | self.paste_button.clicked[bool].connect( | |
|
85 | self.paste_requested | |
|
86 | ) | |
|
87 | self.run_button.clicked[bool].connect( | |
|
88 | self.run_requested | |
|
89 | ) | |
|
90 | ||
|
91 | def update_magic_helper(self, visible): | |
|
92 | if not visible or self.data != None: | |
|
93 | return | |
|
94 | self.data = {} | |
|
95 | self.search_class.clear() | |
|
96 | self.search_class.addItem("Populating...") | |
|
97 | self.main_window.active_frontend._silent_exec_callback( | |
|
98 | 'get_ipython().magic("lsmagic")', | |
|
99 | self.populate_magic_helper | |
|
100 | ) | |
|
101 | ||
|
102 | def populate_magic_helper(self, data): | |
|
103 | if not data: | |
|
104 | return | |
|
105 | ||
|
106 | if data['status'] != 'ok': | |
|
107 | self.main_window.log.warn( | |
|
108 | "%%lsmagic user-expression failed: {}".format(data) | |
|
109 | ) | |
|
110 | return | |
|
111 | ||
|
112 | self.search_class.clear() | |
|
113 | self.search_list.clear() | |
|
114 | ||
|
115 | self.data = json.loads( | |
|
116 | data['data'].get('application/json', {}) | |
|
117 | ) | |
|
118 | ||
|
119 | self.search_class.addItem('All Magics', 'any') | |
|
120 | classes = set() | |
|
121 | ||
|
122 | for mtype in sorted(self.data): | |
|
123 | subdict = self.data[mtype] | |
|
124 | for name in sorted(subdict): | |
|
125 | classes.add(subdict[name]) | |
|
126 | ||
|
127 | for cls in sorted(classes): | |
|
128 | label = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>", cls) | |
|
129 | self.search_class.addItem(label, cls) | |
|
130 | ||
|
131 | self.filter_magic_helper('.', 'any') | |
|
132 | ||
|
133 | def class_selected(self, index): | |
|
134 | item = self.search_class.itemData(index) | |
|
135 | regex = self.search_line.text() | |
|
136 | self.filter_magic_helper(regex = regex, cls = item) | |
|
137 | ||
|
138 | def search_changed(self, search_string): | |
|
139 | item = self.search_class.itemData( | |
|
140 | self.search_class.currentIndex() | |
|
141 | ) | |
|
142 | self.filter_magic_helper(regex = search_string, cls = item) | |
|
143 | ||
|
144 | def _get_current_search_item(self, item = None): | |
|
145 | text = None | |
|
146 | if not isinstance(item, QtGui.QListWidgetItem): | |
|
147 | item = self.search_list.currentItem() | |
|
148 | text = item.text() | |
|
149 | return text | |
|
150 | ||
|
151 | def paste_requested(self, item = None): | |
|
152 | text = self._get_current_search_item(item) | |
|
153 | if text != None: | |
|
154 | self.pasteRequested.emit(text) | |
|
155 | ||
|
156 | def run_requested(self, item = None): | |
|
157 | text = self._get_current_search_item(item) | |
|
158 | if text != None: | |
|
159 | self.runRequested.emit(text) | |
|
160 | ||
|
161 | def filter_magic_helper(self, regex, cls): | |
|
162 | if regex == "" or regex == None: | |
|
163 | regex = '.' | |
|
164 | if cls == None: | |
|
165 | cls = 'any' | |
|
166 | ||
|
167 | self.search_list.clear() | |
|
168 | for mtype in sorted(self.data): | |
|
169 | subdict = self.data[mtype] | |
|
170 | prefix = magic_escapes[mtype] | |
|
171 | ||
|
172 | for name in sorted(subdict): | |
|
173 | mclass = subdict[name] | |
|
174 | pmagic = prefix + name | |
|
175 | ||
|
176 | if (re.match(regex, name) or re.match(regex, pmagic)) and \ | |
|
177 | (cls == 'any' or cls == mclass): | |
|
178 | self.search_list.addItem(pmagic) | |
|
179 |
@@ -699,157 +699,39 b' class MainWindow(QtGui.QMainWindow):' | |||
|
699 | 699 | self.add_menu_action(self.help_menu, self.onlineHelpAct) |
|
700 | 700 | |
|
701 | 701 | def init_magic_helper(self): |
|
702 | self.magic_helper_data = None | |
|
703 | self.magic_helper = QtGui.QDockWidget("Show Magics", self) | |
|
702 | from .magic_helper import MagicHelper | |
|
703 | ||
|
704 | self.magic_helper = MagicHelper("Show Magics", self) | |
|
705 | ||
|
704 | 706 | self.magic_helper.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea | |
|
705 | 707 | QtCore.Qt.RightDockWidgetArea) |
|
706 | 708 | self.magic_helper.setVisible(False) |
|
707 | 709 | |
|
708 | class MinListWidget(QtGui.QListWidget): | |
|
709 | def sizeHint(self): | |
|
710 | s = QtCore.QSize() | |
|
711 | s.setHeight(super(MinListWidget,self).sizeHint().height()) | |
|
712 | s.setWidth(self.sizeHintForColumn(0)) | |
|
713 | return s | |
|
714 | ||
|
715 | self.magic_helper_frame = QtGui.QFrame() | |
|
716 | self.magic_helper_searchl = QtGui.QLabel("Search:") | |
|
717 | self.magic_helper_search = QtGui.QLineEdit() | |
|
718 | self.magic_helper_class = QtGui.QComboBox() | |
|
719 | self.magic_helper_list = MinListWidget() | |
|
720 | self.magic_helper_paste = QtGui.QPushButton("Paste") | |
|
721 | self.magic_helper_run = QtGui.QPushButton("Run") | |
|
722 | ||
|
723 | main_layout = QtGui.QVBoxLayout() | |
|
724 | search_layout = QtGui.QHBoxLayout() | |
|
725 | search_layout.addWidget(self.magic_helper_searchl) | |
|
726 | search_layout.addWidget(self.magic_helper_search, 10) | |
|
727 | main_layout.addLayout(search_layout) | |
|
728 | main_layout.addWidget(self.magic_helper_class) | |
|
729 | main_layout.addWidget(self.magic_helper_list, 10) | |
|
730 | action_layout = QtGui.QHBoxLayout() | |
|
731 | action_layout.addWidget(self.magic_helper_paste) | |
|
732 | action_layout.addWidget(self.magic_helper_run) | |
|
733 | main_layout.addLayout(action_layout) | |
|
734 | ||
|
735 | self.magic_helper_frame.setLayout(main_layout) | |
|
736 | self.magic_helper.setWidget(self.magic_helper_frame) | |
|
737 | ||
|
738 | self.magic_helper.visibilityChanged[bool].connect( | |
|
739 | self.update_magic_helper | |
|
740 | ) | |
|
741 | self.magic_helper_class.activated[int].connect( | |
|
742 | self.magic_helper_class_selected | |
|
743 | ) | |
|
744 | self.magic_helper_search.textChanged[str].connect( | |
|
745 | self.magic_helper_search_changed | |
|
746 | ) | |
|
747 | self.magic_helper_list.itemDoubleClicked[QtGui.QListWidgetItem].connect( | |
|
748 | self.magic_helper_paste_requested | |
|
749 | ) | |
|
750 | self.magic_helper_paste.clicked[bool].connect( | |
|
710 | self.magic_helper.pasteRequested[str].connect( | |
|
751 | 711 | self.magic_helper_paste_requested |
|
752 | 712 | ) |
|
753 |
self.magic_helper |
|
|
713 | self.magic_helper.runRequested[str].connect( | |
|
754 | 714 | self.magic_helper_run_requested |
|
755 | 715 | ) |
|
756 | 716 | |
|
757 | 717 | self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.magic_helper) |
|
758 | 718 | |
|
759 | def update_magic_helper(self, visible): | |
|
760 | if not visible or self.magic_helper_data != None: | |
|
761 | return | |
|
762 | self.magic_helper_data = {} | |
|
763 | self.magic_helper_class.clear() | |
|
764 | self.magic_helper_class.addItem("Populating...") | |
|
765 | self.active_frontend._silent_exec_callback( | |
|
766 | 'get_ipython().magic("lsmagic")', | |
|
767 | self.populate_magic_helper | |
|
768 | ) | |
|
769 | ||
|
770 | def populate_magic_helper(self, data): | |
|
771 | if not data: | |
|
772 | return | |
|
773 | ||
|
774 | if data['status'] != 'ok': | |
|
775 | self.log.warn("%%lsmagic user-expression failed: {}".format(data)) | |
|
776 | return | |
|
777 | ||
|
778 | self.magic_helper_class.clear() | |
|
779 | self.magic_helper_list.clear() | |
|
780 | ||
|
781 | self.magic_helper_data = json.loads( | |
|
782 | data['data'].get('application/json', {}) | |
|
783 | ) | |
|
784 | ||
|
785 | self.magic_helper_class.addItem('All Magics', 'any') | |
|
786 | classes = set() | |
|
787 | ||
|
788 | for mtype in sorted(self.magic_helper_data): | |
|
789 | subdict = self.magic_helper_data[mtype] | |
|
790 | for name in sorted(subdict): | |
|
791 | classes.add(subdict[name]) | |
|
792 | ||
|
793 | for cls in sorted(classes): | |
|
794 | label = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>", cls) | |
|
795 | self.magic_helper_class.addItem(label, cls) | |
|
796 | ||
|
797 | self.filter_magic_helper('.', 'any') | |
|
798 | ||
|
799 | def magic_helper_class_selected(self, index): | |
|
800 | item = self.magic_helper_class.itemData(index) | |
|
801 | regex = self.magic_helper_search.text() | |
|
802 | self.filter_magic_helper(regex = regex, cls = item) | |
|
803 | ||
|
804 | def magic_helper_search_changed(self, search_string): | |
|
805 | item = self.magic_helper_class.itemData( | |
|
806 | self.magic_helper_class.currentIndex() | |
|
807 | ) | |
|
808 | self.filter_magic_helper(regex = search_string, cls = item) | |
|
809 | ||
|
810 | def _magic_helper_get_current(self, item = None): | |
|
811 | text = None | |
|
812 | if not isinstance(item, QtGui.QListWidgetItem): | |
|
813 | item = self.magic_helper_list.currentItem() | |
|
814 | text = item.text() | |
|
815 | return text | |
|
816 | ||
|
817 | 719 | def _set_active_frontend_focus(self): |
|
818 | 720 | # this is a hack, self.active_frontend._control seems to be |
|
819 | 721 | # a private member. Unfortunately this is the only method |
|
820 | 722 | # to set focus reliably |
|
821 | 723 | QtCore.QTimer.singleShot(200, self.active_frontend._control.setFocus) |
|
822 | 724 | |
|
823 |
def magic_helper_paste_requested(self, |
|
|
824 | text = self._magic_helper_get_current(item) | |
|
725 | def magic_helper_paste_requested(self, text = None): | |
|
825 | 726 | if text != None: |
|
826 | 727 | self.active_frontend.input_buffer = text |
|
827 | 728 | self._set_active_frontend_focus() |
|
828 | 729 | |
|
829 |
def magic_helper_run_requested(self, |
|
|
830 | text = self._magic_helper_get_current(item) | |
|
730 | def magic_helper_run_requested(self, text = None): | |
|
831 | 731 | if text != None: |
|
832 | 732 | self.active_frontend.execute(text) |
|
833 | 733 | self._set_active_frontend_focus() |
|
834 | 734 | |
|
835 | def filter_magic_helper(self, regex, cls): | |
|
836 | if regex == "" or regex == None: | |
|
837 | regex = '.' | |
|
838 | if cls == None: | |
|
839 | cls = 'any' | |
|
840 | ||
|
841 | self.magic_helper_list.clear() | |
|
842 | for mtype in sorted(self.magic_helper_data): | |
|
843 | subdict = self.magic_helper_data[mtype] | |
|
844 | prefix = magic_escapes[mtype] | |
|
845 | ||
|
846 | for name in sorted(subdict): | |
|
847 | mclass = subdict[name] | |
|
848 | pmagic = prefix + name | |
|
849 | ||
|
850 | if (re.match(regex, name) or re.match(regex, pmagic)) and \ | |
|
851 | (cls == 'any' or cls == mclass): | |
|
852 | self.magic_helper_list.addItem(pmagic) | |
|
853 | 735 | |
|
854 | 736 | |
|
855 | 737 | # minimize/maximize/fullscreen actions: |
General Comments 0
You need to be logged in to leave comments.
Login now