Modules |
Files |
Inheritance Tree |
Inheritance Graph |
Name Index |
Config
File: Synopsis/UI/Qt/actionwiz.py
1| # $Id: actionwiz.py,v 1.7 2002/11/11 14:28:41 chalky Exp $
2| #
3| # This file is a part of Synopsis.
4| # Copyright (C) 2000, 2001 Stefan Seefeld
5| # Copyright (C) 2000, 2001 Stephen Davies
6| #
7| # Synopsis is free software; you can redistribute it and/or modify it
8| # under the terms of the GNU General Public License as published by
9| # the Free Software Foundation; either version 2 of the License, or
10| # (at your option) any later version.
11| #
12| # This program is distributed in the hope that it will be useful,
13| # but WITHOUT ANY WARRANTY; without even the implied warranty of
14| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15| # General Public License for more details.
16| #
17| # You should have received a copy of the GNU General Public License
18| # along with this program; if not, write to the Free Software
19| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20| # 02111-1307, USA.
21| #
22| # $Log: actionwiz.py,v $
23| # Revision 1.7 2002/11/11 14:28:41 chalky
24| # New Source Edit dialog, with Insert Rule wizard and new s/path/rules with
25| # extra features.
26| #
27| # Revision 1.6 2002/07/11 09:30:16 chalky
28| # Added page for configuring the cacher action
29| #
30| # Revision 1.5 2002/07/04 06:44:41 chalky
31| # Can now edit define list for C++ Parser.
32| #
33| # Revision 1.4 2002/06/22 07:03:27 chalky
34| # Updates to GUI - better editing of Actions, and can now execute some.
35| #
36| # Revision 1.3 2002/04/26 01:21:14 chalky
37| # Bugs and cleanups
38| #
39| # Revision 1.2 2001/11/09 08:06:59 chalky
40| # More GUI fixes and stuff. Double click on Source Actions for a dialog.
41| #
42| # Revision 1.1 2001/11/05 06:52:11 chalky
43| # Major backside ui changes
44| #
45|
46|
47| import sys, pickle, Synopsis, cStringIO, types, copy
48| from qt import *
49| from Synopsis import Config
50| from Synopsis.Core import AST, Util
51| from Synopsis.Core.Action import *
52| from Synopsis.Formatter.ASCII import ASCIIFormatter
53| from Synopsis.Formatter.ClassTree import ClassTree
54|
55| from sourceoptionsdialog import SourceOptionsDialog
56| from sourceinsertwizard import SourceInsertWizard
57| from sourceeditexclude import SourceEditExclude
58| from sourceeditsimple import SourceEditSimple
59| from sourceeditglob import SourceEditGlob
60|
61| class Struct:
62| "Dummy class. Initialise with keyword args."
63| def __init__(self, **keys):
64| for name, value in keys.items(): setattr(self, name, value)
65|
66| def make_relative(base, file):
67| """Useful function to make a filename relative to the given base
68| filename"""
69| if file[:len(base)] == base:
70| # Simple case: base is a prefix of file
71| return file[len(base):]
72| elif file and file[0] == '/' and base[0] == '/':
73| # More complex case: must insert ../../'s
74| fs = string.split(file, os.sep)
75| bs = string.split(base[:-1], os.sep)
76| while len(fs) and len(bs) and fs[0] == bs[0]:
77| del fs[0]
78| del bs[0]
79| fs = ['..']*len(bs) + fs
80| return string.join(fs, os.sep)
81| # If either is not absolute, can't assume anything
82| return file
83|
84| class SourceActionEditor:
85| """Presents a modal GUI for editing a Source Action"""
86| def __init__(self, parent, project):
87| self.parent = parent
88| self.project = project
89| self.action = None
90| self.dialog = None
91| self.insert_wizard = None
92| self.rules = None
93| self.rule_list_items = []
94| def edit(self, action):
95| """Edits the given action"""
96| self.action = action
97| self.init_dialog()
98| result = self.dialog.exec_loop()
99| if result:
100| self.keep_changes()
101| print "result is",result
102| def keep_changes(self):
103| dialog, action = self.dialog, self.action
104| # If the name is changed, must use Project to rename
105| name = str(dialog.NameEdit.text())
106| if action.name() != name:
107| print "Setting name to ",name
108| self.project.actions().rename_action(action, name)
109| self.project.actions().action_changed(action)
110| # Copy the list of rules back into the action
111| self.action.rules()[:] = self.rules
112|
113| def init_dialog(self):
114| dialog, action = self.dialog, self.action
115| if not self.dialog:
116| dialog = self.dialog = SourceOptionsDialog(self.parent, "source", 1)
117| self.dialog.action = action
118| dialog.connect(dialog.RuleList, SIGNAL("selectionChanged()"), self.on_selection)
119| dialog.connect(dialog.InsertButton, SIGNAL("clicked()"), self.on_insert)
120| dialog.connect(dialog.DeleteButton, SIGNAL("clicked()"), self.on_delete)
121| dialog.connect(dialog.EditButton, SIGNAL("clicked()"), self.on_edit)
122| dialog.connect(dialog.DownButton, SIGNAL("clicked()"), self.on_down)
123| dialog.connect(dialog.UpButton, SIGNAL("clicked()"), self.on_up)
124| dialog.connect(dialog.TestButton, SIGNAL("clicked()"), self.on_test)
125| # Deep copy list of source rules
126| self.rules = map(lambda x: x.clone(), action.rules())
127| # Set fields from the Action
128| dialog.NameEdit.setText(action.name())
129| self.update_list()
130|
131| def on_insert(self):
132| # Run the wizard
133| self.insert_wiz = wiz = SourceInsertWizard(self.dialog)
134| # Add connections
135| wiz.connect(wiz, SIGNAL("selected(const QString&)"), self.on_insert_finished)
136| wiz.CurrentDirectoryLabel.setText('Current Directory: '+os.getcwd())
137| result = wiz.exec_loop()
138| print "Wizard result:",result
139| # Deal with result
140| if not result: return
141| base = None
142| if wiz.relative_option == 1:
143| base = os.getcwd()
144| elif wiz.relative_option == 2:
145| base = str(wiz.RelativeThisEdit.text())
146| if base and base[-1] != '/': base = base + '/'
147| print "Base is:",base
148| if wiz.rule_type == "simple":
149| files = []
150| item = wiz.FileList.firstChild()
151| while item:
152| file = str(item.text(0))
153| file = make_relative(base, file)
154| files.append(file)
155| item = item.nextSibling()
156| self.rules.append(SimpleSourceRule(files))
157| elif wiz.rule_type == "exclude":
158| glob = str(wiz.ExcludeCombo.currentText())
159| self.rules.append(ExcludeSourceRule(glob))
160| else:
161| dirs = []
162| item = wiz.DirList.firstChild()
163| while item:
164| dir = str(item.text(0))
165| dir = make_relative(base, dir)
166| dirs.append(dir)
167| item = item.nextSibling()
168| glob = str(wiz.FileGlobEdit.text())
169| if wiz.rule_type == 'glob':
170| self.rules.append(GlobSourceRule(dirs, glob, 0))
171| else:
172| self.rules.append(GlobSourceRule(dirs, glob, 1))
173|
174| # Update the list
175| self.update_list()
176|
177| def on_insert_finished(self, title):
178| if str(title) != "Finished":
179| return
180| # Figure out text to put in the "rule info" box
181| wiz = self.insert_wiz
182| base = None
183| if wiz.relative_option == 1:
184| base = os.getcwd()
185| elif wiz.relative_option == 2:
186| base = str(wiz.RelativeThisEdit.text())
187| if base and base[-1] != '/': base = base + '/'
188| print "Base is:",base
189| if wiz.rule_type == "simple":
190| files = []
191| item = wiz.FileList.firstChild()
192| while item:
193| file = str(item.text(0))
194| file = make_relative(base, file)
195| files.append(file)
196| item = item.nextSibling()
197| wiz.RuleInfo.setText("Type: Simple\n"+
198| "Files:\n "+string.join(files, "\n "))
199| elif wiz.rule_type == "exclude":
200| wiz.RuleInfo.setText("Type: Exclude\n"+
201| "Glob expression:\n "+str(wiz.ExcludeCombo.currentText()))
202| else:
203| # Glob or recursive glob
204| dirs = []
205| item = wiz.DirList.firstChild()
206| while item:
207| dir = str(item.text(0))
208| dir = make_relative(base, dir)
209| dirs.append(dir)
210| item = item.nextSibling()
211| wiz.RuleInfo.setText("Type: %s\n"%wiz.rule_type +
212| "Directories:\n "+string.join(dirs, "\n ")+
213| "\nFiles:\n "+str(wiz.FileGlobEdit.text()))
214|
215| def update_list(self):
216| dialog, action = self.dialog, self.action
217| dialog.RuleList.clear()
218| items = []
219| self.__rule_list_items = items
220| # NB: we must reverse the list before adding (and then unreverse the
221| # items list later). I don't know why QListView adds things at the top
222| # rather than the bottom, and I sure hope it doesn't change later on!
223| rules = list(self.rules)
224| rules.reverse()
225| for rule in rules:
226| if rule.type == 'Simple':
227| if len(rule.files) <= 1:
228| dir, name = os.path.split(rule.files[0])
229| items.append(QListViewItem(dialog.RuleList,
230| 'Simple', dir, name, ''))
231| else:
232| # Make into a tree
233| items.append(QListViewItem(dialog.RuleList,
234| 'Simple', '', '(%d files)'%len(rule.files), ''))
235| for file in rule.files:
236| dir, name = os.path.split(file)
237| QListViewItem(items[-1], '', dir, name, '')
238| elif rule.type == 'Glob':
239| recursive = ''
240| if rule.recursive: recursive = 'recursive'
241| if len(rule.dirs) <= 1:
242| items.append(QListViewItem(dialog.RuleList,
243| 'Glob', string.join(rule.dirs, '\n'),
244| rule.glob, recursive))
245| else:
246| items.append(QListViewItem(dialog.RuleList,
247| 'Glob', '(%d directories)'%len(rule.dirs),
248| rule.glob, recursive))
249| for dir in rule.dirs:
250| QListViewItem(items[-1], '', dir, '', '')
251| elif rule.type == 'Exclude':
252| items.append(QListViewItem(dialog.RuleList,
253| 'Exclude', rule.glob, '', ''))
254| items.reverse()
255|
256| def get_selected_index(self):
257| """Returns the index of the selected item, or None"""
258| sel_item = self.dialog.RuleList.selectedItem()
259| if not sel_item: return None
260| index = self.__rule_list_items.index(sel_item)
261| if index == -1:
262| print "Warning: QListViewItem not found in list"
263| return None
264| return index
265|
266| def on_selection(self):
267| dialog = self.dialog
268| index = self.get_selected_index()
269| if index != None:
270| dialog.DeleteButton.setEnabled(1)
271| dialog.EditButton.setEnabled(1)
272| if index > 0:
273| dialog.UpButton.setEnabled(1)
274| else:
275| dialog.UpButton.setEnabled(0)
276| if index < len(self.rules) - 1:
277| dialog.DownButton.setEnabled(1)
278| else:
279| dialog.DownButton.setEnabled(0)
280| else:
281| dialog.DeleteButton.setEnabled(0)
282| dialog.EditButton.setEnabled(0)
283| dialog.UpButton.setEnabled(0)
284| dialog.DownButton.setEnabled(0)
285|
286| def on_delete(self):
287| index = self.get_selected_index()
288| if index is None: return
289| button = QMessageBox.warning(self.dialog, "Confirm delete",
290| "Really delete this source rule?",
291| QMessageBox.Yes, QMessageBox.No)
292| if button == QMessageBox.Yes:
293| del self.rules[index]
294| self.update_list()
295| self.on_selection()
296|
297| def on_up(self):
298| index = self.get_selected_index()
299| if index is None or index == 0: return
300| # Swap with rule above
301| this_rule = self.rules[index]
302| self.rules[index] = self.rules[index-1]
303| self.rules[index-1] = this_rule
304| self.update_list()
305| self.dialog.RuleList.setSelected(self.__rule_list_items[index-1], 1)
306|
307| def on_down(self):
308| index = self.get_selected_index()
309| if index is None or index == len(self.rules)-1: return
310| # Swap with rule below
311| this_rule = self.rules[index]
312| self.rules[index] = self.rules[index+1]
313| self.rules[index+1] = this_rule
314| self.update_list()
315| self.dialog.RuleList.setSelected(self.__rule_list_items[index+1], 1)
316|
317| def on_edit(self):
318| index = self.get_selected_index()
319| if index is None: return
320| rule = self.rules[index]
321| if rule.type == 'Simple':
322| # Bring up the dialog for editing simple rules
323| dlg = SourceEditSimple(self.dialog)
324| dlg.make_relative = make_relative
325| # Copy files from rule into ListView
326| for file in rule.files:
327| QListViewItem(dlg.FileList, file)
328| result = dlg.exec_loop()
329| if not result: return
330| # Copy ListView back into rule
331| rule.files = []
332| item = dlg.FileList.firstChild()
333| while item:
334| rule.files.append(str(item.text(0)))
335| item = item.nextSibling()
336| elif rule.type == 'Glob':
337| # Bring up the dialog for editing glob rules
338| dlg = SourceEditGlob(self.dialog)
339| dlg.make_relative = make_relative
340| # Copy dirs from rule into ListView
341| for dir in rule.dirs:
342| QListViewItem(dlg.DirList, dir)
343| dlg.FileGlobEdit.setText(rule.glob)
344| dlg.RecurseCheckBox.setChecked(rule.recursive)
345| result = dlg.exec_loop()
346| if not result: return
347| # Copy ListView, edit and checkbox back into rule
348| rule.dirs = []
349| item = dlg.DirList.firstChild()
350| while item:
351| rule.dirs.append(str(item.text(0)))
352| item = item.nextSibling()
353| rule.glob = str(dlg.FileGlobEdit.text())
354| rule.recursive = dlg.RecurseCheckBox.isChecked()
355| elif rule.type == 'Exclude':
356| # Bring up the dialog for editing exclude rules
357| dlg = SourceEditExclude(self.dialog)
358| # Copy glob into dlg
359| dlg.GlobEdit.setText(rule.glob)
360| result = dlg.exec_loop()
361| if not result: return
362| # Copy glob from dialog back into rule
363| rule.glob = str(dlg.GlobEdit.text())
364| # Update the display with new info
365| self.update_list()
366|
367| def on_test(self):
368| from Synopsis.Core.Executor import ExecutorCreator
369| project = self.project
370| source_exec = ExecutorCreator(project).create(self.action)
371| names = source_exec.get_output_names()
372| names = map(lambda x: x[0], names)
373| dialog = QDialog(self.dialog, 'test results', 1)
374| dialog.setCaption('Files matched by source action %s'%self.action.name())
375| dialog.setMinimumSize(600,400)
376| vbox = QVBoxLayout(dialog)
377| listbox = QListBox(dialog)
378| listbox.insertStrList(names)
379| button = QPushButton('&Close', dialog)
380| dialog.connect(button, SIGNAL('clicked()'), dialog.accept)
381| vbox.addWidget(listbox)
382| vbox.addWidget(button)
383| dialog.exec_loop()
384|
385| class CacherPage (QVBox):
386| """The Page (portion of dialog) that displays options for a Cacher
387| Action"""
388| def __init__(self, parent, action_container):
389| QVBox.__init__(self, parent)
390| self.__ac = action_container
391| self._make_layout()
392| def title(self):
393| return "Cache"
394| def action(self):
395| return self.__ac.action
396| def set_action(self, action):
397| self.__ac.action = action
398| def _make_layout(self):
399| self.setSpacing(4)
400| label = QLabel("<p>Cacher actions can load a stored AST from disk, "+
401| "or cache generated AST's between other actions (eg: parsing and "+
402| "linking, or linking and formatting).", self)
403|
404| # Make action name field
405| hbox = QHBox(self)
406| label = QLabel("Action &name:", hbox)
407| self.action_name = QLineEdit(hbox)
408| QToolTip.add(self.action_name, "A unique name for this Action")
409| label.setBuddy(self.action_name)
410| self.connect(self.action_name, SIGNAL('textChanged(const QString&)'), self.onActionName)
411| hbox.setSpacing(4)
412|
413| # Make options
414| bgroup = QVButtonGroup("Type of Cacher:", self)
415| self.bfile = QRadioButton("Load a single AST file", bgroup)
416| self.bdir = QRadioButton("Cache inputs to many files below a directory", bgroup)
417|
418| label = QLabel("Select the file to load:", self)
419| hbox = QHBox(self)
420| self.line_file = QLineEdit('', hbox)
421| self.brfile = QPushButton('Browse', hbox)
422| label = QLabel("Select the directory to cache files in:", self)
423| hbox = QHBox(self)
424| self.line_dir = QLineEdit('', hbox)
425| self.brdir = QPushButton('Browse', hbox)
426|
427| self.connect(self.bfile, SIGNAL('clicked()'), self.onButtonFile)
428| self.connect(self.bdir, SIGNAL('clicked()'), self.onButtonDir)
429| self.connect(self.line_file, SIGNAL('textChanged(const QString&)'), self.onFileChanged)
430| self.connect(self.line_dir, SIGNAL('textChanged(const QString&)'), self.onDirChanged)
431| self.connect(self.brfile, SIGNAL('clicked()'), self.onBrowseFile)
432| self.connect(self.brdir, SIGNAL('clicked()'), self.onBrowseDir)
433|
434| def pre_show(self):
435| action = self.action()
436| self.action_name.setText(action.name())
437| self.line_file.setText(str(action.file or ''))
438| self.line_dir.setText(str(action.dir or ''))
439|
440| if not action.file:
441| self.line_file.setEnabled(0)
442| self.brfile.setEnabled(0)
443| elif not action.dir:
444| self.line_dir.setEnabled(0)
445| self.brdir.setEnabled(0)
446| if action.file:
447| self.bfile.setOn(1)
448| elif action.dir:
449| self.bdir.setOn(1)
450|
451| def showEvent(self, ev):
452| QVBox.showEvent(self, ev)
453| # Focus name field
454| self.action_name.setFocus()
455| self.action_name.selectAll()
456|
457| def onActionName(self, name):
458| self.action().set_name(str(name))
459|
460| def onButtonFile(self):
461| self.action().dir = ''
462| self.line_file.setEnabled(1)
463| self.brfile.setEnabled(1)
464| self.line_dir.setEnabled(0)
465| self.brdir.setEnabled(0)
466|
467| def onButtonDir(self):
468| self.action().file = ''
469| self.line_dir.setEnabled(1)
470| self.brdir.setEnabled(1)
471| self.line_file.setEnabled(0)
472| self.brfile.setEnabled(0)
473|
474| def onFileChanged(self, text):
475| text = str(text)
476| print "file =",text
477| self.action().file = text
478|
479| def onDirChanged(self, text):
480| text = str(text)
481| print "dir =",text
482| self.action().dir = text
483|
484| def onBrowseFile(self):
485| result = QFileDialog.getOpenFileName(str(self.action().file or ''), '*.syn',
486| self, 'open', 'Select an AST file to load from')
487| result = str(result)
488| if result:
489| self.line_file.setText(result)
490|
491| def onBrowseDir(self):
492| result = QFileDialog.getExistingDirectory(str(self.action().dir or ''),
493| self, 'open', 'Select an AST file to load from')
494| result = str(result)
495| if result:
496| self.line_dir.setText(result)
497|
498| class ParserPage (QVBox):
499| """The Page (portion of dialog) that displays options for a Parser
500| Action"""
501| def __init__(self, parent, action_container):
502| QVBox.__init__(self, parent)
503| self.__ac = action_container
504| self._make_layout()
505| def title(self):
506| return "Module"
507| def action(self):
508| return self.__ac.action
509| def set_action(self, action):
510| self.__ac.action = action
511| def _make_layout(self):
512| self.setSpacing(4)
513| label = QLabel("<p>Parser actions take a list of source files from "+
514| "connected Source actions, and pass them through a parser "+
515| "by one.", self)
516|
517| # Make action name field
518| hbox = QHBox(self)
519| label = QLabel("Action &name:", hbox)
520| self.source_name = QLineEdit(hbox)
521| QToolTip.add(self.source_name, "A unique name for this Action")
522| label.setBuddy(self.source_name)
523| self.connect(self.source_name, SIGNAL('textChanged(const QString&)'), self.onActionName)
524| hbox.setSpacing(4)
525|
526| # Make module type area
527| label = QLabel("Select a &Parser module to use:", self)
528| self.module_list = QListBox(self)
529| label.setBuddy(self.module_list)
530| self.module_items = {}
531| for name in ['C++', 'Python', 'IDL']:
532| # Listbox item is automatically added to the list
533| item = QListBoxText(self.module_list, name)
534| self.module_items[name] = item
535| self.connect(self.module_list, SIGNAL('clicked(QListBoxItem*)'), self.onModuleSelected)
536|
537| def pre_show(self):
538| self.source_name.setText(self.action().name())
539| config = self.action().config()
540| if config and self.module_items.has_key(config.name):
541| self.module_list.setCurrentItem(self.module_items[config.name])
542|
543| def showEvent(self, ev):
544| QVBox.showEvent(self, ev)
545| # Focus name field
546| self.source_name.setFocus()
547| self.source_name.selectAll()
548|
549| def onActionName(self, name):
550| self.action().set_name(str(name))
551|
552| def onModuleSelected(self, item):
553| module = str(item.text()) #un-QString-ify it
554| print module,"selected"
555| action = self.action()
556| config = action.config()
557| if config is None:
558| # New action, create empty struct
559| config = Struct()
560| action.set_config(config)
561| else:
562| # Confirm with user
563| button = QMessageBox.information(self, "Confirm module change",
564| "Warning: Changing the module type may cause configuration\n"+\
565| "settings to be lost. Are you sure?", QMessageBox.Yes,
566| QMessageBox.No)
567| print button
568| if button == QMessageBox.No:
569| print "User aborted"
570| return
571| # Changing module for existing action - copy attributes from
572| # example config if they're not already set
573| config.name = module
574| copy = Config.Base.Parser.modules[module]
575| for attr, value in copy.__dict__.items():
576| if not hasattr(config, attr):
577| setattr(config, attr, value)
578|
579| class CppParserPage (QVBox):
580| """The Page (portion of dialog) that displays options for a C++ Parser Action"""
581| def __init__(self, parent, action_container):
582| QVBox.__init__(self, parent)
583| self.__ac = action_container
584| self._make_layout()
585| def title(self):
586| return "C++ Options"
587| def action(self):
588| return self.__ac.action
589| def set_action(self, action):
590| self.__ac.action = action
591| def _make_layout(self):
592| self.setSpacing(4)
593| config = self.action().config()
594|
595| # Make options area
596| if not hasattr(config, 'main_file'): config.main_file = 0
597| self.main_only = QCheckBox("Include declarations from &main file only", self)
598| self.main_only.setChecked(config.main_file)
599| self.connect(self.main_only, SIGNAL('toggled(bool)'), self.onMainFile)
600| # Make include path area
601| label = QLabel("&Include paths:", self)
602| hbox = QHBox(self)
603| self.path_list = QListBox(hbox)
604| label.setBuddy(self.path_list)
605| self._update_path_list()
606| self.connect(self.path_list, SIGNAL('clicked(QListBoxItem*)'), self.onPathSelected)
607|
608| # Make buttons
609| bbox = QVBox(hbox)
610| bbox.setSpacing(4)
611| self.badd = QPushButton('&Add', bbox)
612| self.bremove = QPushButton('&Remove', bbox)
613| self.bgcc = QPushButton('Add &GCC paths', bbox)
614| QToolTip.add(self.badd, "Add a new path setting")
615| QToolTip.add(self.bremove, "Remove the selected path setting")
616| self.connect(self.badd, SIGNAL('clicked()'), self.onAddPath)
617| self.connect(self.bremove, SIGNAL('clicked()'), self.onRemovePath)
618| self.connect(self.bgcc, SIGNAL('clicked()'), self.onAddGCC)
619|
620| # Make Define area
621| label = QLabel("Preprocessor &defines:", self)
622| hbox = QHBox(self)
623| self.def_list = QListBox(hbox)
624| label.setBuddy(self.def_list)
625| self._update_def_list()
626| self.connect(self.def_list, SIGNAL('clicked(QListBoxItem*)'), self.onDefSelected)
627|
628| # Make buttons
629| bbox = QVBox(hbox)
630| bbox.setSpacing(4)
631| self.bdadd = QPushButton('&Add', bbox)
632| self.bdremove = QPushButton('&Remove', bbox)
633| QToolTip.add(self.bdadd, "Add a new define")
634| QToolTip.add(self.bdremove, "Remove the selected define")
635| self.connect(self.bdadd, SIGNAL('clicked()'), self.onAddDefine)
636| self.connect(self.bdremove, SIGNAL('clicked()'), self.onRemoveDefine)
637|
638|
639| def _update_path_list(self):
640| config = self.action().config()
641| self.path_list.clear()
642| self.path_items = {}
643| if not hasattr(config, 'include_path') or \
644| type(config.include_path) != type([]):
645| config.include_path = []
646| for path in config.include_path:
647| # Listbox item is automatically added to the list
648| print "path:",path
649| item = QListBoxText(self.path_list, path)
650| self.path_items[path] = item
651|
652| def _update_def_list(self):
653| config = self.action().config()
654| self.def_list.clear()
655| self.def_items = {}
656| if not hasattr(config, 'defines') or \
657| type(config.defines) != type([]):
658| config.defines = []
659| for define in config.defines:
660| # Listbox item is automatically added to the list
661| print "Define:", define
662| item = QListBoxText(self.def_list, define)
663| self.def_items[define] = item
664|
665| def pre_show(self):
666| pass
667|
668| def showEvent(self, ev):
669| QVBox.showEvent(self, ev)
670| # Focus name field
671| #self.source_name.setFocus()
672| #self.source_name.selectAll()
673|
674| def onPathSelected(self, item):
675| path = item.text()
676|
677| def onDefSelected(self, item):
678| pass
679|
680| def onAddPath(self):
681| dir = QFileDialog.getExistingDirectory()
682| dir = str(dir)
683| if dir:
684| self.action().config().include_path.append(dir)
685| self._update_path_list()
686|
687| def onRemovePath(self):
688| pass
689|
690| def onAddGCC(self):
691| config = self.action().config()
692| config.include_path.extend([
693| '/usr/include/g++-3/',
694| '/usr/lib/gcc-lib/i386-linux/2.95.4/include'
695| ])
696| print "config:",config.include_path
697| self._update_list()
698|
699| def onMainFile(self, on):
700| self.action().config().main_file = not not on
701|
702| def onAddDefine(self):
703| define, okay = QInputDialog.getText("Add define",
704| "Enter the new define. Eg: 'DEBUG' or 'MODE=1' without quotes.")
705| define = str(define)
706| if okay and define:
707| self.action().config().defines.append(define)
708| self._update_def_list()
709|
710| def onRemoveDefine(self):
711| curr = self.def_list.currentItem()
712| if curr < 0: return
713| del self.action().config().defines[curr]
714|
715| class FormatterPage (QVBox):
716| """The Page (portion of dialog) that displays options for a Formatter
717| Action"""
718| def __init__(self, parent, action_container):
719| QVBox.__init__(self, parent)
720| self.__ac = action_container
721| self._make_layout()
722| def title(self):
723| return "Module"
724| def action(self):
725| return self.__ac.action
726| def set_action(self, action):
727| self.__ac.action = action
728| def _make_layout(self):
729| self.setSpacing(4)
730|
731| # Make action name field
732| hbox = QHBox(self)
733| label = QLabel("Formatter Action &name:", hbox)
734| self.formatter_name = QLineEdit(hbox)
735| QToolTip.add(self.formatter_name, "A unique name for this Formatter Action")
736| label.setBuddy(self.formatter_name)
737| self.connect(self.formatter_name, SIGNAL('textChanged(const QString&)'), self.onActionName)
738| hbox.setSpacing(4)
739|
740| def pre_show(self):
741| self.formatter_name.setText(self.action().name())
742|
743| def showEvent(self, ev):
744| QVBox.showEvent(self, ev)
745| # Focus name field
746| self.formatter_name.setFocus()
747| self.formatter_name.selectAll()
748|
749| def onActionName(self, name):
750| self.action().set_name(str(name))
751|
752|
753| class DetailsPage (QVBox):
754| """The Page (portion of dialog) that displays config details"""
755| def __init__(self, parent, action_container):
756| QVBox.__init__(self, parent)
757| self.__ac = action_container
758| self._make_layout()
759| def title(self):
760| return "Details"
761| def action(self):
762| return self.__ac.action
763| def set_action(self, action):
764| self.__ac.action = action
765| def _make_layout(self):
766| self.setSpacing(4)
767| label = QLabel("<p>Configuration options are listed below. Most have"+
768| "comments which you can see by holding your mouse over them.", self)
769| # Make properties area
770| self.listview = QListView(self)
771| self.listview.addColumn('Property', 100)
772| self.listview.addColumn('Value')
773| self.listview.setRootIsDecorated(1)
774|
775| def pre_show(self):
776| # Fill properties table
777| self.listview.clear()
778| config = self.action().config()
779| self._fill_list(config, self.listview)
780|
781| def _fill_list(self, obj, parent):
782| attrs = obj.__dict__.keys()
783| attrs.sort()
784| for attr in attrs:
785| if attr in ('__name__', '__doc__', '__module__'):
786| continue
787| value = getattr(obj, attr)
788| if type(value) in (types.InstanceType, types.ClassType):
789| item = QListViewItem(parent, attr)
790| self._fill_list(value, item)
791| else:
792| item = QListViewItem(parent, attr, str(value))
793|
794|
795| class ActionDialog (QTabDialog):
796| def __init__(self, parent, action, project):
797| QTabDialog.__init__(self, parent, 'Action Properties', 1,
798| Qt.WStyle_Customize | Qt.WStyle_NormalBorder | Qt.WStyle_Title)
799| self.setCaption('Action Properties')
800| self.project = project
801| self.action = action
802| self.setMinimumSize(600,400)
803| self.page_classes = self._get_pages()
804| if not self.page_classes: return
805| self.pages = []
806| for page_class in self.page_classes:
807| page = page_class(self, self)
808| self.pages.append(page)
809| self.addTab(page, page.title())
810| page.pre_show()
811| self.setOkButton("Close")
812| self.exec_loop()
813| self.project.actions().action_changed(self.action)
814|
815| def _get_pages(self):
816| if isinstance(self.action, SourceAction):
817| return [SourcePage]
818| elif isinstance(self.action, ParserAction):
819| return [ParserPage, CppParserPage, DetailsPage]
820| elif isinstance(self.action, FormatAction):
821| return [FormatterPage, DetailsPage]
822| elif isinstance(self.action, CacherAction):
823| return [CacherPage]
824| return None
825|
826|
827|
828| class AddWizard (QWizard):
829| def __init__(self, parent, action, project):
830| QWizard.__init__(self, parent, 'AddWizard', 1)
831| self.setMinimumSize(600,400)
832| self.action = action
833| self.project = project
834|
835| self.title_font = QFont('Helvetica', 20, QFont.Bold)
836| self.title_palette = QPalette(self.palette())
837| self.title_palette.setColor(QPalette.Normal, QColorGroup.Foreground, Qt.white)
838| self.title_palette.setColor(QPalette.Normal, QColorGroup.Background, Qt.darkBlue)
839| self.title_label = QLabel('', self)
840| self.title_label.setFrameStyle(QFrame.Panel | QFrame.Sunken)
841| self.title_label.setLineWidth(1)
842| self.title_label.setMidLineWidth(1)
843| self.title_label.setPalette(self.title_palette)
844| self.title_label.setFont(self.title_font)
845|
846| self._makeStartPage()
847| self._makeSourcePage()
848| self._makeParserPage()
849| self._makeCacherPage()
850| self._makeFormatterPage()
851| self._makeFormatPropPage()
852| # Finish page goes last
853| self._makeFinishPage()
854|
855| def _makeStartPage(self):
856| vbox = QVBox(self)
857| vbox.layout().setSpacing(4)
858| QLabel('What type of action would you like to create?', vbox)
859| group = QButtonGroup(1, Qt.Horizontal, "&Action type", vbox)
860| radio_source = QRadioButton('&Source - Specifies, monitors and loads source code files', group)
861| radio_parser = QRadioButton('&Parser - Reads source files and parses them into ASTs', group)
862| radio_linker = QRadioButton('&Linker - Operates on one or more ASTs, typically linking various things together', group)
863| radio_cacher = QRadioButton('File &Cache - Stores an AST to disk to speed reprocessing', group)
864| radio_format = QRadioButton('&Formatter - Exports an AST to some format, eg: HTML, PDF', group)
865| self.__action_type_group = group
866| self.__action_types = {
867| radio_source: 'source', radio_parser: 'parser', radio_linker: 'linker',
868| radio_format: 'format', radio_cacher: 'cacher' }
869| self.connect(group, SIGNAL('clicked(int)'), self.onActionType)
870|
871| self.addPage(vbox, "Select action type")
872| self.setNextEnabled(vbox, 0)
873| self.setHelpEnabled(vbox, 0)
874| self.start = vbox
875| self.old_action_type = None
876|
877| def onActionType(self, id):
878| t = self.__action_types[self.__action_type_group.find(id)]
879| old = self.action
880| x, y, name = old.x(), old.y(), old.name()
881|
882| if name[0:4] == 'New ' and name[-7:] == ' action':
883| name = 'New %s action' % t
884|
885| # Make old page not appropriate
886| if self.old_action_type and self.old_action_type != t:
887| o = self.old_action_type
888| if o == 'source': self.setAppropriate(self.source, 0)
889| elif o == 'parser': self.setAppropriate(self.parser, 0)
890| elif o == 'linker': pass
891| elif o == 'cacher': self.setAppropriate(self.cacher, 0)
892| elif o == 'format':
893| self.setAppropriate(self.formatter, 0)
894| self.setAppropriate(self.formatProp, 0)
895| self.old_action_type = t
896|
897| # Create Action depending on selection and set next page
898| if t == 'source':
899| self.action = SourceAction(x, y, name)
900| self.setAppropriate(self.source, 1)
901| elif t == 'parser':
902| self.action = ParserAction(x, y, name)
903| self.setAppropriate(self.parser, 1)
904| elif t == 'linker':
905| self.action = LinkerAction(x, y, name)
906| # Find the class in the config dictionary
907| config_class = Config.Base.Linker.modules['Linker']
908| config = Struct(name=config_class.name)
909| self.action.set_config( config )
910| # Copy attributes from example config
911| self._copyAttrs(config, config_class, 1)
912| elif t == 'cacher':
913| self.action = CacherAction(x, y, name)
914| self.setAppropriate(self.cacher, 1)
915| elif t == 'format':
916| self.action = FormatAction(x, y, name)
917| self.setAppropriate(self.formatter, 1)
918|
919| if self.action is not old:
920| # allow user to move to next screen if Action changed
921| self.setNextEnabled(self.start, 1)
922|
923| def _makeSourcePage(self):
924| page = SourcePage(self, self)
925| self.addPage(page, "Source action config")
926| self.setHelpEnabled(page, 0)
927| self.setAppropriate(page, 0)
928| self.source = page
929|
930| def _makeParserPage(self):
931| page = ParserPage(self, self)
932| self.addPage(page, "Parser action config")
933| self.setHelpEnabled(page, 0)
934| self.setAppropriate(page, 0)
935| self.parser = page
936|
937| def _makeCacherPage(self):
938| page = CacherPage(self, self)
939| self.addPage(page, "Cacher action config")
940| self.setHelpEnabled(page, 0)
941| self.setAppropriate(page, 0)
942| self.cacher = page
943|
944| def _makeFormatterPage(self):
945| vbox = QVBox(self)
946| vbox.layout().setSpacing(4)
947| QLabel('Which Formatter module do you want to use?', vbox)
948| group = QButtonGroup(1, Qt.Horizontal, "&Formatter module", vbox)
949| radio_html = QRadioButton('&HTML - Generates a static website', group)
950| radio_docbook = QRadioButton('&DocBook - Generates a DocBook book', group)
951| radio_texinfo = QRadioButton('&TexInfo - Generates a TexInfo manual', group)
952| radio_dia = QRadioButton('D&ia - Generates diagrams for Dia', group)
953| radio_dot = QRadioButton('&GraphViz - Generates diagrams with GraphViz', group)
954| radio_dump = QRadioButton('D&ump - Dumps the AST for debugging', group)
955| radio_other = QRadioButton('&Other - Choose any module (case matters)', group)
956| self.formatter_edit_other = QLineEdit('<module name>', vbox)
957| self.formatter_edit_other.setEnabled(0)
958| self.__format_module_group = group
959| self.__format_modules = {
960| radio_html: 'HTML', radio_docbook: 'DocBook',
961| radio_texinfo: 'TexInfo', radio_dia: 'Dia', radio_dot: 'Dot',
962| radio_dump: 'DUMP', radio_other: 'other' }
963| self.connect(group, SIGNAL('clicked(int)'), self.onFormatModule)
964|
965| self.addPage(vbox, "Select Formatter Module")
966| self.setNextEnabled(vbox, 0)
967| self.setHelpEnabled(vbox, 0)
968| self.setAppropriate(vbox, 0)
969| self.formatter = vbox
970|
971| def onFormatModule(self, id):
972| # Find out which option was selected
973| t = self.__format_modules[self.__format_module_group.find(id)]
974|
975| # Decide which page to go to next..
976| if t == 'other':
977| self.formatter_edit_other.setEnabled(1)
978| # Create an empty config with the specified name
979| name = self.formatter_edit_other.text()
980| self.action.set_config(Struct(name=name))
981| else:
982| self.formatter_edit_other.setEnabled(0)
983| # Find the class in the config dictionary
984| config_class = Config.Base.Formatter.modules[t]
985| config = Struct(name=config_class.name)
986| self.action.set_config( config )
987| # Copy attributes from example config
988| self._copyAttrs(config, config_class, 1)
989|
990|
991| # Enable the Next button
992| self.setNextEnabled(self.formatter, 1)
993| self.setAppropriate(self.formatProp, 1)
994|
995| def _copyAttrs(self, dest, src, overwrite):
996| """Copies attributes from src to dest objects. src may be a class, and
997| recursive structs/classes are copied properly. Note that the copy
998| module doesn't copy classes, so we do that bit ourselves"""
999| for attr, value in src.__dict__.items():
1000| if type(value) in (types.InstanceType, types.ClassType):
1001| if not hasattr(dest, attr): setattr(dest, attr, Struct())
1002| self._copyAttrs(getattr(dest, attr), value, overwrite)
1003| elif type(value) in (types.FunctionType,):
1004| continue
1005| elif attr in ('__doc__', '__module__', '__name__'):
1006| continue
1007| elif overwrite or not hasattr(config, attr):
1008| setattr(dest, attr, copy.deepcopy(value))
1009|
1010| def _makeFormatPropPage(self):
1011| page = FormatterPage(self, self)
1012| self.addPage(page, "Formatter action properties")
1013| self.setHelpEnabled(page, 0)
1014| self.setAppropriate(page, 0)
1015| self.formatProp = page
1016|
1017| def onActionName(self, name):
1018| self.action.set_name(name)
1019|
1020| def onSourceAddPath(self, name):
1021| pass
1022|
1023| def _makeFinishPage(self):
1024| vbox = QVBox(self)
1025| QLabel('You have finished creating your new action.', vbox)
1026| self.addPage(vbox, "Finished")
1027| self.setFinish(vbox, 1)
1028| self.setFinishEnabled(vbox, 1)
1029| self.setHelpEnabled(vbox, 0)
1030| self.finish = vbox
1031|
1032| def showPage(self, page):
1033| if page in (self.source, self.formatProp):
1034| page.pre_show()
1035| QWizard.showPage(self, page)
1036|
1037| if page is self.finish:
1038| self.finishButton().setFocus()
1039|
1040| def layOutTitleRow(self, hbox, title):
1041| self.title_label.setText(title)
1042| hbox.add(self.title_label)
1043|
1044|