Merged r9481:9518 on jblum/grc_reorganize into trunk. Reorganized grc source under...
[debian/gnuradio] / grc / src / gui / MainWindow.py
1 """
2 Copyright 2008 Free Software Foundation, Inc.
3 This file is part of GNU Radio
4
5 GNU Radio Companion is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 GNU Radio Companion is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18 """
19
20 from Constants import \
21         MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT, \
22         NEW_FLOGRAPH_TITLE, MAIN_WINDOW_PREFIX, \
23         REPORTS_WINDOW_HEIGHT
24 from .. platforms.base.Constants import FLOW_GRAPH_FILE_EXTENSION
25 from Actions import APPLICATION_QUIT, FLOW_GRAPH_KILL
26 import pygtk
27 pygtk.require('2.0')
28 import gtk
29 import Bars
30 from BlockTreeWindow import BlockTreeWindow
31 from Dialogs import TextDisplay, MessageDialogHelper
32 from DrawingArea import DrawingArea
33 from NotebookPage import NotebookPage
34 import Preferences
35 import Messages
36 import os
37
38 ############################################################
39 # Main window
40 ############################################################
41
42 class MainWindow(gtk.Window):
43         """The topmost window with menus, the tool bar, and other major windows."""
44
45         def __init__(self, handle_states, platform):
46                 """
47                 MainWindow contructor.
48                 @param handle_states the callback function
49                 """
50                 self._platform = platform
51                 #setup window
52                 self.handle_states = handle_states
53                 gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
54                 vbox = gtk.VBox()
55                 hbox = gtk.HBox()
56                 self.add(vbox)
57                 #create the menu bar and toolbar
58                 vbox.pack_start(Bars.MenuBar(), False)
59                 vbox.pack_start(Bars.Toolbar(), False)
60                 #setup scrolled window
61                 self.scrolled_window = gtk.ScrolledWindow()
62                 self.scrolled_window.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
63                 self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
64                 self.drawing_area = DrawingArea(self)
65                 self.scrolled_window.add_with_viewport(self.drawing_area)
66                 #create the notebook
67                 self.notebook = gtk.Notebook()
68                 self.page_to_be_closed = None
69                 self.current_page = None
70                 self.notebook.set_show_border(False)
71                 self.notebook.set_scrollable(True) #scroll arrows for page tabs
72                 self.notebook.connect('switch-page', self._handle_page_change)
73                 fg_and_report_box = gtk.VBox(False, 0)
74                 fg_and_report_box.pack_start(self.notebook, False, False, 0)
75                 fg_and_report_box.pack_start(self.scrolled_window)
76                 hbox.pack_start(fg_and_report_box)
77                 vbox.pack_start(hbox)
78                 #create the side windows
79                 side_box = gtk.VBox()
80                 hbox.pack_start(side_box, False)
81                 side_box.pack_start(BlockTreeWindow(platform, self.get_flow_graph)) #allow resize, selection window can have more space
82                 #create the reports window
83                 self.text_display = TextDisplay()
84                 #house the reports in a scrolled window
85                 self.reports_scrolled_window = gtk.ScrolledWindow()
86                 self.reports_scrolled_window.set_size_request(-1, REPORTS_WINDOW_HEIGHT)
87                 self.reports_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
88                 self.reports_scrolled_window.add_with_viewport(self.text_display)
89                 fg_and_report_box.pack_end(self.reports_scrolled_window, False) #dont allow resize, fg should get all the space
90                 #show all but the main window container and the reports window
91                 vbox.show_all()
92                 self.notebook.hide()
93                 self._show_reports_window(False)
94                 # load preferences and show the main window
95                 Preferences.load(platform)
96                 self.resize(*Preferences.window_size())
97                 self.show()#show after resize in preferences
98
99         ############################################################
100         # Event Handlers
101         ############################################################
102
103         def _quit(self, window, event):
104                 """
105                 Handle the delete event from the main window.
106                 Generated by pressing X to close, alt+f4, or right click+close.
107                 This method in turns calls the state handler to quit.
108                 @return true
109                 """
110                 self.handle_states(APPLICATION_QUIT)
111                 return True
112
113         def _handle_page_change(self, notebook, page, page_num):
114                 """
115                 Handle a page change. When the user clicks on a new tab,
116                 reload the flow graph to update the vars window and
117                 call handle states (select nothing) to update the buttons.
118                 @param notebook the notebook
119                 @param page new page
120                 @param page_num new page number
121                 """
122                 self.current_page = self.notebook.get_nth_page(page_num)
123                 Messages.send_page_switch(self.current_page.get_file_path())
124                 self.handle_states()
125
126         ############################################################
127         # Report Window
128         ############################################################
129
130         def add_report_line(self, line):
131                 """
132                 Place line at the end of the text buffer, then scroll its window all the way down.
133                 @param line the new text
134                 """
135                 self.text_display.insert(line)
136                 vadj = self.reports_scrolled_window.get_vadjustment()
137                 vadj.set_value(vadj.upper)
138                 vadj.emit('changed')
139
140         def _show_reports_window(self, show):
141                 """
142                 Show the reports window when show is True.
143                 Hide the reports window when show is False.
144                 @param show boolean flag
145                 """
146                 if show: self.reports_scrolled_window.show()
147                 else: self.reports_scrolled_window.hide()
148
149         ############################################################
150         # Pages: create and close
151         ############################################################
152
153         def new_page(self, file_path='', show=False):
154                 """
155                 Create a new notebook page.
156                 Set the tab to be selected.
157                 @param file_path optional file to load into the flow graph
158                 @param show true if the page should be shown after loading
159                 """
160                 #if the file is already open, show the open page and return
161                 if file_path and file_path in self._get_files(): #already open
162                         page = self.notebook.get_nth_page(self._get_files().index(file_path))
163                         self._set_page(page)
164                         return
165                 try: #try to load from file
166                         if file_path: Messages.send_start_load(file_path)
167                         flow_graph = self._platform.get_new_flow_graph()
168                         #inject drawing area and handle states into flow graph
169                         flow_graph.drawing_area = self.drawing_area
170                         flow_graph.handle_states = self.handle_states
171                         page = NotebookPage(
172                                 self,
173                                 flow_graph=flow_graph,
174                                 file_path=file_path,
175                         )
176                         if file_path: Messages.send_end_load()
177                 except Exception, e: #return on failure
178                         Messages.send_fail_load(e)
179                         return
180                 #add this page to the notebook
181                 self.notebook.append_page(page, page.get_tab())
182                 try: self.notebook.set_tab_reorderable(page, True)
183                 except: pass #gtk too old
184                 self.notebook.set_tab_label_packing(page, False, False, gtk.PACK_START)
185                 #only show if blank or manual
186                 if not file_path or show: self._set_page(page)
187
188         def close_pages(self):
189                 """
190                 Close all the pages in this notebook.
191                 @return true if all closed
192                 """
193                 open_files = filter(lambda file: file, self._get_files()) #filter blank files
194                 open_file = self.get_page().get_file_path()
195                 #close each page
196                 for page in self._get_pages():
197                         self.page_to_be_closed = page
198                         self.close_page(False)
199                 if self.notebook.get_n_pages(): return False
200                 #save state before closing
201                 Preferences.files_open(open_files)
202                 Preferences.file_open(open_file)
203                 Preferences.window_size(self.get_size())
204                 Preferences.save()
205                 return True
206
207         def close_page(self, ensure=True):
208                 """
209                 Close the current page.
210                 If the notebook becomes empty, and ensure is true,
211                 call new page upon exit to ensure that at least one page exists.
212                 @param ensure boolean
213                 """
214                 if not self.page_to_be_closed: self.page_to_be_closed = self.get_page()
215                 #show the page if it has an executing flow graph or is unsaved
216                 if self.page_to_be_closed.get_pid() or not self.page_to_be_closed.get_saved():
217                         self._set_page(self.page_to_be_closed)
218                 #unsaved? ask the user
219                 if not self.page_to_be_closed.get_saved() and self._save_changes():
220                         self.handle_states(FLOW_GRAPH_SAVE) #try to save
221                         if not self.page_to_be_closed.get_saved(): #still unsaved?
222                                 self.page_to_be_closed = None #set the page to be closed back to None
223                                 return
224                 #stop the flow graph if executing
225                 if self.page_to_be_closed.get_pid(): self.handle_states(FLOW_GRAPH_KILL)
226                 #remove the page
227                 self.notebook.remove_page(self.notebook.page_num(self.page_to_be_closed))
228                 if ensure and self.notebook.get_n_pages() == 0: self.new_page() #no pages, make a new one
229                 self.page_to_be_closed = None #set the page to be closed back to None
230
231         ############################################################
232         # Misc
233         ############################################################
234
235         def update(self):
236                 """
237                 Set the title of the main window.
238                 Set the titles on the page tabs.
239                 Show/hide the reports window.
240                 @param title the window title
241                 """
242                 if self.get_page():
243                         title = ''.join((
244                                         MAIN_WINDOW_PREFIX,
245                                         ' - Editing: ',
246                                         (self.get_page().get_file_path() or NEW_FLOGRAPH_TITLE),
247                                         (self.get_page().get_saved() and ' ' or '*'), #blank must be non empty
248                                 )
249                         )
250                 else: title = MAIN_WINDOW_PREFIX + ' - Editor '
251                 gtk.Window.set_title(self, title)
252                 #set tab titles
253                 for page in self._get_pages():
254                         title = os.path.basename(page.get_file_path())
255                         #strip file extension #TEMP
256                         if title.endswith('.xml'):
257                                 title = title[0:-len('.xml')]
258                         #strip file extension
259                         if title.endswith(FLOW_GRAPH_FILE_EXTENSION):
260                                 title = title[0:-len(FLOW_GRAPH_FILE_EXTENSION)]
261                         page.set_text(''.join((
262                                                 (title or NEW_FLOGRAPH_TITLE),
263                                                 (page.get_saved() and ' ' or '*'), #blank must be non empty
264                                         )
265                                 )
266                         )
267                 #reports window
268                 self._show_reports_window(Preferences.show_reports_window())
269                 #show/hide notebook tabs
270                 if len(self._get_pages()) > 1: self.notebook.show()
271                 else: self.notebook.hide()
272
273         def get_page(self):
274                 """
275                 Get the selected page.
276                 @return the selected page
277                 """
278                 return self.current_page
279
280         def get_flow_graph(self):
281                 """
282                 Get the selected flow graph.
283                 @return the selected flow graph
284                 """
285                 return self.get_page().get_flow_graph()
286
287         ############################################################
288         # Helpers
289         ############################################################
290
291         def _set_page(self, page):
292                 """
293                 Set the current page.
294                 @param page the page widget
295                 """
296                 self.current_page = page
297                 self.notebook.set_current_page(self.notebook.page_num(self.current_page))
298
299         def _save_changes(self):
300                 """
301                 Save changes to flow graph?
302                 @return true if yes
303                 """
304                 return MessageDialogHelper(
305                         gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 'Unsaved Changes!',
306                         'Would you like to save changes before closing?'
307                 ) == gtk.RESPONSE_YES
308
309         def _get_files(self):
310                 """
311                 Get the file names for all the pages, in order.
312                 @return list of file paths
313                 """
314                 return map(lambda page: page.get_file_path(), self._get_pages())
315
316         def _get_pages(self):
317                 """
318                 Get a list of all pages in the notebook.
319                 @return list of pages
320                 """
321                 return [self.notebook.get_nth_page(page_num) for page_num in range(self.notebook.get_n_pages())]