def __init__ (self,parent,close_button=False,error_window=None): QFrame.__init__(self,parent); self._enabled = True; toplo = QVBoxLayout(self); toplo.setContentsMargins(0,0,0,0); toplo.setSpacing(0); self.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding); splitter = QSplitter(Qt.Vertical,self); toplo.addWidget(splitter); splitter.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding); splitter.setChildrenCollapsible(False); # figure out our app_gui parent self._appgui = app_proxy_gui.appgui(parent); # create an editor box editor_box = QFrame(splitter); lo = QVBoxLayout(editor_box); lo.setContentsMargins(0,0,0,0); lo.setSpacing(0); # find main window to associate our toolbar with mainwin = parent; while mainwin and not isinstance(mainwin,QMainWindow): mainwin = mainwin.parent(); self._toolbar = QToolBar(mainwin or self); lo.addWidget(self._toolbar); self._toolbar.setIconSize(QSize(16,16)); #### populate toolbar # Exec button and menu self._tb_tdlexec = QToolButton(self._toolbar); self._toolbar.addWidget(self._tb_tdlexec); self._tb_tdlexec.setIcon(pixmaps.gear.icon()); self._tb_tdlexec.setText("Exec"); self._tb_tdlexec.setToolButtonStyle(Qt.ToolButtonTextBesideIcon); self._tb_tdlexec.setToolTip("Accesses run-time options & jobs defined by this TDL script"); self._tb_tdlexec.hide(); jobs = self._tdlexec_menu = TDLOptionsDialog(self); jobs.setWindowTitle("TDL Jobs & Runtime Options"); jobs.setWindowIcon(pixmaps.gear.icon()); jobs.hide(); QObject.connect(self._tb_tdlexec,SIGNAL("clicked()"),jobs.exec_); # save menu and button self._tb_save = QToolButton(self._toolbar); self._toolbar.addWidget(self._tb_save); self._tb_save.setIcon(pixmaps.file_save.icon()); self._tb_save.setToolTip("Saves script. Click on the down-arrow for other options."); savemenu = QMenu(self); self._tb_save.setMenu(savemenu); self._tb_save.setPopupMode(QToolButton.MenuButtonPopup); self._tb_save._modified_color = QColor("yellow"); qa_save = savemenu.addAction(pixmaps.file_save.icon(),"&Save script",self._save_file); qa_save.setShortcut(Qt.ALT+Qt.Key_S); QObject.connect(self._tb_save,SIGNAL("clicked()"),self._save_file); qa_save_as = savemenu.addAction(pixmaps.file_save.icon(),"Save script &as...", self.curry(self._save_file,save_as=True)); qa_revert = self._qa_revert = savemenu.addAction("Revert to saved",self._revert_to_saved); # run menu and button self._qa_run = self._toolbar.addAction(pixmaps.blue_round_reload.icon(), "&Save & reload",self._import_main_file); self._qa_run.setShortcut(Qt.ALT+Qt.Key_R); # Compile-time options and menu self._tb_opts = QToolButton(self._toolbar); self._toolbar.addWidget(self._tb_opts); self._tb_opts.setIcon(pixmaps.wrench.icon()); self._tb_opts.setText("Options"); self._tb_opts.setToolButtonStyle(Qt.ToolButtonTextBesideIcon); self._tb_opts.setToolTip("Accesses compile-time options for this TDL script"); # self._tb_opts.hide(); opts = self._options_menu = TDLOptionsDialog(self,ok_label="Compile",ok_icon=pixmaps.blue_round_reload); opts.setWindowTitle("TDL Compile-time Options"); opts.setWindowIcon(pixmaps.wrench.icon()); QObject.connect(opts,PYSIGNAL("accepted()"),self._compile_main_file); QObject.connect(TDLOptions.OptionObject,SIGNAL("mandatoryOptionsSet"),self.mark_mandatory_options_set); opts.hide(); QObject.connect(self._tb_opts,SIGNAL("clicked()"),opts.show); # cross-connect the rereshProfiles() signals, so that dialogs can update each other's # profile menus QObject.connect(self._options_menu,PYSIGNAL("refreshProfiles()"),self._tdlexec_menu.refresh_profiles); QObject.connect(self._tdlexec_menu,PYSIGNAL("refreshProfiles()"),self._options_menu.refresh_profiles); self._toolbar.addSeparator(); # cursor position indicator self._poslabel = QLabel(self._toolbar); #wa = QWidgetAction(self._toolbar); #wa.setDefaultWidget(self._poslabel); #self._toolbar.addAction(wa); #self._toolbar.addWidget(self._poslabel); self._toolbar.addWidget(self._poslabel); width = self._poslabel.fontMetrics().width("L:999 C:999"); self._poslabel.setMinimumWidth(width); self._poslabel.setText("L:0 C:0"); # filename indicator self._pathlabel = QLabel(self._toolbar); #wa = QWidgetAction(self._toolbar); #wa.setDefaultWidget(self._pathlabel); self._toolbar.addWidget(self._pathlabel); #self._toolbar.addAction(wa); self._pathlabel.show(); self._pathlabel.setAlignment(Qt.AlignRight|Qt.AlignVCenter); self._pathlabel.setIndent(10); self._pathlabel.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Minimum); if close_button: if not isinstance(close_button,QIcon): close_button = pixmaps.remove.icon(); self._qa_close = self._toolbar.addAction(close_button,"&Close file",self._file_closed); self._qa_close.setShortcut(Qt.ALT+Qt.Key_W); self._pathlabel.setText("(no file)"); #### add editor window self._editor = editor = QTextEdit(editor_box); lo.addWidget(self._editor); editor.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding); editor.setAcceptRichText(False); editor.setLineWrapMode(QTextEdit.NoWrap); self._document = QTextDocument(self); editor.setDocument(self._document); QObject.connect(self._document,SIGNAL("modificationChanged(bool)"),self._text_modified); QObject.connect(self._editor,SIGNAL("cursorPositionChanged()"),self._display_cursor_position); # QObject.connect(self._editor,SIGNAL("textChanged()"),self._clear_transients); # add character formats for error display self._format_error = QTextCharFormat(self._editor.currentCharFormat()); self._format_error.setBackground(QBrush(QColor("lightpink"))); self._format_suberror = QTextCharFormat(self._editor.currentCharFormat()); self._format_suberror.setBackground(QBrush(QColor("lightgrey"))); self._format_current_error = QTextCharFormat(self._editor.currentCharFormat()); self._format_current_error.setBackground(QBrush(QColor("orangered"))); # add message window self._message_box = QFrame(editor_box); lo.addWidget(self._message_box); self._message_box.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Preferred); self._message_box.setFrameStyle(QFrame.Panel+QFrame.Sunken); self._message_box.setLineWidth(2); mblo = QVBoxLayout(self._message_box); mblo.setContentsMargins(0,0,0,0); msgb1 = QHBoxLayout(); mblo.addLayout(msgb1); msgb1.setContentsMargins(0,0,0,0); msgb1.setSpacing(0); self._message_icon = QToolButton(self._message_box); msgb1.addWidget(self._message_icon); self._message = QLabel(self._message_box); msgb1.addWidget(self._message); self._message.setTextFormat(Qt.RichText); self._message.setWordWrap(True); self._message.setMargin(0); self._message.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Preferred); # self._message_icon.setAlignment(Qt.AlignTop); # self._message_icon.setMargin(4); self._message_icon.setAutoRaise(True); self._message_icon.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed); QObject.connect(self._message_icon,SIGNAL("clicked()"),self.clear_message); self._message_icon.setToolTip("Click here to clear the message"); self._message_widgets = []; self._message_transient = False; # figure out if we already have an error box to attach to self._error_window = error_window or getattr(parent,'_tdlgui_error_window',None); if self._error_window: #self._resize_errwin = False; pass; else: # otherwise create an error floater self._error_window = TDLErrorFloat(parent); setattr(parent,'_tdlgui_error_window',self._error_window); # self._resize_errwin = True; QObject.connect(self._error_window,PYSIGNAL("hasErrors()"),self._reset_errors); QObject.connect(self._error_window,PYSIGNAL("showError()"),self.show_error); # set filename self._filename = None; # "official" path of file (None if new script not yet saved) self._mainfile = None; # if not None, then we're "slaved" to a main file (see below) self._file_disktime = None; # modtime on disk when file was loaded self._basename = None; self._modified = False; self._closed = False; self._error_at_line = {}; self._is_tree_in_sync = True; self._pub_nodes = None;
def __init__(self, parent, close_button=False, error_window=None): QFrame.__init__(self, parent) self._enabled = True toplo = QVBoxLayout(self) toplo.setContentsMargins(0, 0, 0, 0) toplo.setSpacing(0) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) splitter = QSplitter(Qt.Vertical, self) toplo.addWidget(splitter) splitter.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) splitter.setChildrenCollapsible(False) # figure out our app_gui parent self._appgui = app_proxy_gui.appgui(parent) # create an editor box editor_box = QFrame(splitter) lo = QVBoxLayout(editor_box) lo.setContentsMargins(0, 0, 0, 0) lo.setSpacing(0) # find main window to associate our toolbar with mainwin = parent while mainwin and not isinstance(mainwin, QMainWindow): mainwin = mainwin.parent() self._toolbar = QToolBar(mainwin or self) lo.addWidget(self._toolbar) self._toolbar.setIconSize(QSize(16, 16)) #### populate toolbar # Exec button and menu self._tb_tdlexec = QToolButton(self._toolbar) self._toolbar.addWidget(self._tb_tdlexec) self._tb_tdlexec.setIcon(pixmaps.gear.icon()) self._tb_tdlexec.setText("Exec") self._tb_tdlexec.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self._tb_tdlexec.setToolTip( "Accesses run-time options & jobs defined by this TDL script") self._tb_tdlexec.hide() jobs = self._tdlexec_menu = TDLOptionsDialog(self) jobs.setWindowTitle("TDL Jobs & Runtime Options") jobs.setWindowIcon(pixmaps.gear.icon()) jobs.hide() QObject.connect(self._tb_tdlexec, SIGNAL("clicked()"), jobs.exec_) # save menu and button self._tb_save = QToolButton(self._toolbar) self._toolbar.addWidget(self._tb_save) self._tb_save.setIcon(pixmaps.file_save.icon()) self._tb_save.setToolTip( "Saves script. Click on the down-arrow for other options.") savemenu = QMenu(self) self._tb_save.setMenu(savemenu) self._tb_save.setPopupMode(QToolButton.MenuButtonPopup) self._tb_save._modified_color = QColor("yellow") qa_save = savemenu.addAction(pixmaps.file_save.icon(), "&Save script", self._save_file) qa_save.setShortcut(Qt.ALT + Qt.Key_S) QObject.connect(self._tb_save, SIGNAL("clicked()"), self._save_file) qa_save_as = savemenu.addAction( pixmaps.file_save.icon(), "Save script &as...", self.curry(self._save_file, save_as=True)) qa_revert = self._qa_revert = savemenu.addAction( "Revert to saved", self._revert_to_saved) # run menu and button self._qa_run = self._toolbar.addAction( pixmaps.blue_round_reload.icon(), "&Save & reload", self._import_main_file) self._qa_run.setShortcut(Qt.ALT + Qt.Key_R) # Compile-time options and menu self._tb_opts = QToolButton(self._toolbar) self._toolbar.addWidget(self._tb_opts) self._tb_opts.setIcon(pixmaps.wrench.icon()) self._tb_opts.setText("Options") self._tb_opts.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self._tb_opts.setToolTip( "Accesses compile-time options for this TDL script") # self._tb_opts.hide(); opts = self._options_menu = TDLOptionsDialog( self, ok_label="Compile", ok_icon=pixmaps.blue_round_reload) opts.setWindowTitle("TDL Compile-time Options") opts.setWindowIcon(pixmaps.wrench.icon()) QObject.connect(opts, PYSIGNAL("accepted()"), self._compile_main_file) QObject.connect(TDLOptions.OptionObject, SIGNAL("mandatoryOptionsSet"), self.mark_mandatory_options_set) opts.hide() QObject.connect(self._tb_opts, SIGNAL("clicked()"), opts.show) # cross-connect the rereshProfiles() signals, so that dialogs can update each other's # profile menus QObject.connect(self._options_menu, PYSIGNAL("refreshProfiles()"), self._tdlexec_menu.refresh_profiles) QObject.connect(self._tdlexec_menu, PYSIGNAL("refreshProfiles()"), self._options_menu.refresh_profiles) self._toolbar.addSeparator() # cursor position indicator self._poslabel = QLabel(self._toolbar) #wa = QWidgetAction(self._toolbar); #wa.setDefaultWidget(self._poslabel); #self._toolbar.addAction(wa); #self._toolbar.addWidget(self._poslabel); self._toolbar.addWidget(self._poslabel) width = self._poslabel.fontMetrics().width("L:999 C:999") self._poslabel.setMinimumWidth(width) self._poslabel.setText("L:0 C:0") # filename indicator self._pathlabel = QLabel(self._toolbar) #wa = QWidgetAction(self._toolbar); #wa.setDefaultWidget(self._pathlabel); self._toolbar.addWidget(self._pathlabel) #self._toolbar.addAction(wa); self._pathlabel.show() self._pathlabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self._pathlabel.setIndent(10) self._pathlabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) if close_button: if not isinstance(close_button, QIcon): close_button = pixmaps.remove.icon() self._qa_close = self._toolbar.addAction(close_button, "&Close file", self._file_closed) self._qa_close.setShortcut(Qt.ALT + Qt.Key_W) self._pathlabel.setText("(no file)") #### add editor window self._editor = editor = QTextEdit(editor_box) lo.addWidget(self._editor) editor.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) editor.setAcceptRichText(False) editor.setLineWrapMode(QTextEdit.NoWrap) self._document = QTextDocument(self) editor.setDocument(self._document) QObject.connect(self._document, SIGNAL("modificationChanged(bool)"), self._text_modified) QObject.connect(self._editor, SIGNAL("cursorPositionChanged()"), self._display_cursor_position) # QObject.connect(self._editor,SIGNAL("textChanged()"),self._clear_transients); # add character formats for error display self._format_error = QTextCharFormat(self._editor.currentCharFormat()) self._format_error.setBackground(QBrush(QColor("lightpink"))) self._format_suberror = QTextCharFormat( self._editor.currentCharFormat()) self._format_suberror.setBackground(QBrush(QColor("lightgrey"))) self._format_current_error = QTextCharFormat( self._editor.currentCharFormat()) self._format_current_error.setBackground(QBrush(QColor("orangered"))) # add message window self._message_box = QFrame(editor_box) lo.addWidget(self._message_box) self._message_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self._message_box.setFrameStyle(QFrame.Panel + QFrame.Sunken) self._message_box.setLineWidth(2) mblo = QVBoxLayout(self._message_box) mblo.setContentsMargins(0, 0, 0, 0) msgb1 = QHBoxLayout() mblo.addLayout(msgb1) msgb1.setContentsMargins(0, 0, 0, 0) msgb1.setSpacing(0) self._message_icon = QToolButton(self._message_box) msgb1.addWidget(self._message_icon) self._message = QLabel(self._message_box) msgb1.addWidget(self._message) self._message.setTextFormat(Qt.RichText) self._message.setWordWrap(True) self._message.setMargin(0) self._message.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) # self._message_icon.setAlignment(Qt.AlignTop); # self._message_icon.setMargin(4); self._message_icon.setAutoRaise(True) self._message_icon.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) QObject.connect(self._message_icon, SIGNAL("clicked()"), self.clear_message) self._message_icon.setToolTip("Click here to clear the message") self._message_widgets = [] self._message_transient = False # figure out if we already have an error box to attach to self._error_window = error_window or getattr( parent, '_tdlgui_error_window', None) if self._error_window: #self._resize_errwin = False; pass else: # otherwise create an error floater self._error_window = TDLErrorFloat(parent) setattr(parent, '_tdlgui_error_window', self._error_window) # self._resize_errwin = True; QObject.connect(self._error_window, PYSIGNAL("hasErrors()"), self._reset_errors) QObject.connect(self._error_window, PYSIGNAL("showError()"), self.show_error) # set filename self._filename = None # "official" path of file (None if new script not yet saved) self._mainfile = None # if not None, then we're "slaved" to a main file (see below) self._file_disktime = None # modtime on disk when file was loaded self._basename = None self._modified = False self._closed = False self._error_at_line = {} self._is_tree_in_sync = True self._pub_nodes = None
class TDLEditor (QFrame,PersistentCurrier): SupportsLineNumbers = False; # a single editor always has the focus current_editor = None; # flag: sync to external editor external_sync = True; def set_external_sync (value): global _external_sync; _external_sync = value; set_external_sync = staticmethod(set_external_sync); def __init__ (self,parent,close_button=False,error_window=None): QFrame.__init__(self,parent); self._enabled = True; toplo = QVBoxLayout(self); toplo.setContentsMargins(0,0,0,0); toplo.setSpacing(0); self.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding); splitter = QSplitter(Qt.Vertical,self); toplo.addWidget(splitter); splitter.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding); splitter.setChildrenCollapsible(False); # figure out our app_gui parent self._appgui = app_proxy_gui.appgui(parent); # create an editor box editor_box = QFrame(splitter); lo = QVBoxLayout(editor_box); lo.setContentsMargins(0,0,0,0); lo.setSpacing(0); # find main window to associate our toolbar with mainwin = parent; while mainwin and not isinstance(mainwin,QMainWindow): mainwin = mainwin.parent(); self._toolbar = QToolBar(mainwin or self); lo.addWidget(self._toolbar); self._toolbar.setIconSize(QSize(16,16)); #### populate toolbar # Exec button and menu self._tb_tdlexec = QToolButton(self._toolbar); self._toolbar.addWidget(self._tb_tdlexec); self._tb_tdlexec.setIcon(pixmaps.gear.icon()); self._tb_tdlexec.setText("Exec"); self._tb_tdlexec.setToolButtonStyle(Qt.ToolButtonTextBesideIcon); self._tb_tdlexec.setToolTip("Accesses run-time options & jobs defined by this TDL script"); self._tb_tdlexec.hide(); jobs = self._tdlexec_menu = TDLOptionsDialog(self); jobs.setWindowTitle("TDL Jobs & Runtime Options"); jobs.setWindowIcon(pixmaps.gear.icon()); jobs.hide(); QObject.connect(self._tb_tdlexec,SIGNAL("clicked()"),jobs.exec_); # save menu and button self._tb_save = QToolButton(self._toolbar); self._toolbar.addWidget(self._tb_save); self._tb_save.setIcon(pixmaps.file_save.icon()); self._tb_save.setToolTip("Saves script. Click on the down-arrow for other options."); savemenu = QMenu(self); self._tb_save.setMenu(savemenu); self._tb_save.setPopupMode(QToolButton.MenuButtonPopup); self._tb_save._modified_color = QColor("yellow"); qa_save = savemenu.addAction(pixmaps.file_save.icon(),"&Save script",self._save_file); qa_save.setShortcut(Qt.ALT+Qt.Key_S); QObject.connect(self._tb_save,SIGNAL("clicked()"),self._save_file); qa_save_as = savemenu.addAction(pixmaps.file_save.icon(),"Save script &as...", self.curry(self._save_file,save_as=True)); qa_revert = self._qa_revert = savemenu.addAction("Revert to saved",self._revert_to_saved); # run menu and button self._qa_run = self._toolbar.addAction(pixmaps.blue_round_reload.icon(), "&Save & reload",self._import_main_file); self._qa_run.setShortcut(Qt.ALT+Qt.Key_R); # Compile-time options and menu self._tb_opts = QToolButton(self._toolbar); self._toolbar.addWidget(self._tb_opts); self._tb_opts.setIcon(pixmaps.wrench.icon()); self._tb_opts.setText("Options"); self._tb_opts.setToolButtonStyle(Qt.ToolButtonTextBesideIcon); self._tb_opts.setToolTip("Accesses compile-time options for this TDL script"); # self._tb_opts.hide(); opts = self._options_menu = TDLOptionsDialog(self,ok_label="Compile",ok_icon=pixmaps.blue_round_reload); opts.setWindowTitle("TDL Compile-time Options"); opts.setWindowIcon(pixmaps.wrench.icon()); QObject.connect(opts,PYSIGNAL("accepted()"),self._compile_main_file); QObject.connect(TDLOptions.OptionObject,SIGNAL("mandatoryOptionsSet"),self.mark_mandatory_options_set); opts.hide(); QObject.connect(self._tb_opts,SIGNAL("clicked()"),opts.show); # cross-connect the rereshProfiles() signals, so that dialogs can update each other's # profile menus QObject.connect(self._options_menu,PYSIGNAL("refreshProfiles()"),self._tdlexec_menu.refresh_profiles); QObject.connect(self._tdlexec_menu,PYSIGNAL("refreshProfiles()"),self._options_menu.refresh_profiles); self._toolbar.addSeparator(); # cursor position indicator self._poslabel = QLabel(self._toolbar); #wa = QWidgetAction(self._toolbar); #wa.setDefaultWidget(self._poslabel); #self._toolbar.addAction(wa); #self._toolbar.addWidget(self._poslabel); self._toolbar.addWidget(self._poslabel); width = self._poslabel.fontMetrics().width("L:999 C:999"); self._poslabel.setMinimumWidth(width); self._poslabel.setText("L:0 C:0"); # filename indicator self._pathlabel = QLabel(self._toolbar); #wa = QWidgetAction(self._toolbar); #wa.setDefaultWidget(self._pathlabel); self._toolbar.addWidget(self._pathlabel); #self._toolbar.addAction(wa); self._pathlabel.show(); self._pathlabel.setAlignment(Qt.AlignRight|Qt.AlignVCenter); self._pathlabel.setIndent(10); self._pathlabel.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Minimum); if close_button: if not isinstance(close_button,QIcon): close_button = pixmaps.remove.icon(); self._qa_close = self._toolbar.addAction(close_button,"&Close file",self._file_closed); self._qa_close.setShortcut(Qt.ALT+Qt.Key_W); self._pathlabel.setText("(no file)"); #### add editor window self._editor = editor = QTextEdit(editor_box); lo.addWidget(self._editor); editor.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding); editor.setAcceptRichText(False); editor.setLineWrapMode(QTextEdit.NoWrap); self._document = QTextDocument(self); editor.setDocument(self._document); QObject.connect(self._document,SIGNAL("modificationChanged(bool)"),self._text_modified); QObject.connect(self._editor,SIGNAL("cursorPositionChanged()"),self._display_cursor_position); # QObject.connect(self._editor,SIGNAL("textChanged()"),self._clear_transients); # add character formats for error display self._format_error = QTextCharFormat(self._editor.currentCharFormat()); self._format_error.setBackground(QBrush(QColor("lightpink"))); self._format_suberror = QTextCharFormat(self._editor.currentCharFormat()); self._format_suberror.setBackground(QBrush(QColor("lightgrey"))); self._format_current_error = QTextCharFormat(self._editor.currentCharFormat()); self._format_current_error.setBackground(QBrush(QColor("orangered"))); # add message window self._message_box = QFrame(editor_box); lo.addWidget(self._message_box); self._message_box.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Preferred); self._message_box.setFrameStyle(QFrame.Panel+QFrame.Sunken); self._message_box.setLineWidth(2); mblo = QVBoxLayout(self._message_box); mblo.setContentsMargins(0,0,0,0); msgb1 = QHBoxLayout(); mblo.addLayout(msgb1); msgb1.setContentsMargins(0,0,0,0); msgb1.setSpacing(0); self._message_icon = QToolButton(self._message_box); msgb1.addWidget(self._message_icon); self._message = QLabel(self._message_box); msgb1.addWidget(self._message); self._message.setTextFormat(Qt.RichText); self._message.setWordWrap(True); self._message.setMargin(0); self._message.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Preferred); # self._message_icon.setAlignment(Qt.AlignTop); # self._message_icon.setMargin(4); self._message_icon.setAutoRaise(True); self._message_icon.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed); QObject.connect(self._message_icon,SIGNAL("clicked()"),self.clear_message); self._message_icon.setToolTip("Click here to clear the message"); self._message_widgets = []; self._message_transient = False; # figure out if we already have an error box to attach to self._error_window = error_window or getattr(parent,'_tdlgui_error_window',None); if self._error_window: #self._resize_errwin = False; pass; else: # otherwise create an error floater self._error_window = TDLErrorFloat(parent); setattr(parent,'_tdlgui_error_window',self._error_window); # self._resize_errwin = True; QObject.connect(self._error_window,PYSIGNAL("hasErrors()"),self._reset_errors); QObject.connect(self._error_window,PYSIGNAL("showError()"),self.show_error); # set filename self._filename = None; # "official" path of file (None if new script not yet saved) self._mainfile = None; # if not None, then we're "slaved" to a main file (see below) self._file_disktime = None; # modtime on disk when file was loaded self._basename = None; self._modified = False; self._closed = False; self._error_at_line = {}; self._is_tree_in_sync = True; self._pub_nodes = None; def __del__ (self): self.has_focus(False); def disable_editor (self): """Called before disabling the editor, as on some versions of PyQt the object is not destroyed properly and keeps receving signals"""; self._enabled = False; QObject.disconnect(TDLOptions.OptionObject,SIGNAL("mandatoryOptionsSet"),self.mark_mandatory_options_set); def show_compile_options (self): self._options_menu.show(); def mark_mandatory_options_set (self,enabled): self._options_menu.enableOkButton(enabled); def show_runtime_options (self): self._tdlexec_menu.show(); def tree_is_in_sync (self,sync=True): """Tells the editor wheether the current tree is in sync with the content of the script. This is indicated by a visual cue on the toolbar. """; if sync: self._tb_tdlexec.setIcon(pixmaps.gear.icon()); self._tb_tdlexec.setToolTip("Access run-time options & jobs defined by this TDL script"); else: self._tb_tdlexec.setIcon(pixmaps.exclaim_yellow_warning.icon()); self._tb_tdlexec.setToolTip("""Access run-time options & jobs defined by this TDL script. Warning! You have modified the script since it was last compiled, so the tree may be out of date."""); def _file_closed (self): self.emit(PYSIGNAL("fileClosed()"),self); def hideEvent (self,ev): self.emit(PYSIGNAL("hidden()")); self.emit(PYSIGNAL("visible()"),False); return QFrame.hideEvent(self,ev); def showEvent (self,ev): self.emit(PYSIGNAL("shown()")); self.emit(PYSIGNAL("visible()"),True); return QFrame.showEvent(self,ev); def hide_jobs_menu (self,dum=False): if self._closed: return; self._tb_tdlexec.hide(); self.clear_message(); def show_line_numbers (self,show): pass; def show_run_control (self,show=True): if self._closed: return; self._qa_run.setVisible(show); def enable_controls (self,enable=True): if self._closed: return; self._qa_run.setEnabled(enable); self._tb_tdlexec.setEnabled(enable); if not enable: self.clear_message(); def disable_controls (self,disable=True): if self._closed: return; self._qa_run.setDisabled(disable); self._tb_tdlexec.setDisabled(disable); if disable: self.clear_message(); def get_filename (self): return self._filename; def get_mainfile (self): return self._mainfile; def _import_main_file (self): # self._tb_opts.setOn(False); self.clear_errors(); if self._document.isModified(): self._save_file(); self.emit(PYSIGNAL("importFile()"),self,self._mainfile or self._filename); def _compile_main_file (self): # self._tb_opts.setOn(False); self.clear_errors(); if self._document.isModified(): self._save_file(); self.emit(PYSIGNAL("compileFile()"),self,self._mainfile or self._filename); def _clear_transients (self): """if message box contains a transient message, clears it"""; if self._message_transient: self.clear_message(); def _display_cursor_position (self): cursor = self._editor.textCursor(); pos = cursor.position(); col = cursor.columnNumber(); line = cursor.blockNumber(); self._poslabel.setText("L:<b>%d</b> C:<b>%d</b>" % (line+1,col+1)); self._poslabel.repaint(); def _text_modified (self,mod=True): self._modified = mod; self.emit(PYSIGNAL("textModified()"),self,bool(mod)); self._tb_save.setAutoRaise(not mod); self._qa_revert.setEnabled(mod); #if mod: #self._tb_save.setBackgroundRole(QPalette.ToolTipBase); #else: #self._tb_save.setBackgroundRole(QPalette.Button); if self._filename: label = '<b>' + self._basename + '</b>'; self._pathlabel.setToolTip(self._filename); else: label = ''; self._pathlabel.setToolTip(''); if self._readonly: label = '[r/o] ' + label; if mod: self._clear_transients(); label = '[mod] ' + label; self._pathlabel.setText(label); def clear_message (self): # traceback.print_stack(); self._message_box.hide(); self._message.setText(''); self._message_icon.setText(''); self._message_transient = False; if self._message_widgets: dum = QWidget(); for w in self._message_widgets: w.reparent(dum); self._message_widgets = []; def show_message (self,msg,error=False,icon=None,transient=False): """Shows message in box. If icon is not None, overrides standard icon. If error=True, uses error icon (icon overrides this). If transient=True, message will be cleared when text is edited. """; self._message.setText(msg); if not icon: if error: icon = pixmaps.red_round_cross.icon(); else: icon = pixmaps.info_blue_round.icon(); self._message_icon.setIcon(icon); self._message_box.show(); self._message_transient = transient; self.clear_errors(); def messagebox (): return self._message_box; def add_message_widget (self,widget): self._mblo.addWidget(widget); self._message_widgets.append(widget); def clear_errors (self): self._error_window.clear_errors(emit_signal=True); def _find_block_by_linenumber (self,line): block = block0 = self._document.begin(); nblock = 1; while block and nblock < line: block0 = block; block = block0.next(); nblock += 1; return block or block0; def _reset_errors (self,nerr): """helper method, resets error markers and such. Usually tied to a hasErrors() signal from an error window"""; if not self._enabled: return; self._error_at_line = {}; self._error_selections = []; self._current_error_selection = QTextEdit.ExtraSelection(); nerr_local = 0; if nerr: error_locations = self._error_window.get_error_locations(); for err_num,filename,line,column,suberror in error_locations: if filename == self._filename: nerr_local += 1; # make cursor corresponding to line containing the error cursor = QTextCursor(self._find_block_by_linenumber(line)); cursor.select(QTextCursor.LineUnderCursor); # make selection object qsel = QTextEdit.ExtraSelection(); qsel.cursor = cursor; if suberror: qsel.format = self._format_suberror; else: qsel.format = self._format_error; # insert into error_at_line list self._error_at_line[line-1] = len(self._error_selections),cursor; # append to list of error selections self._error_selections.append(qsel); self._editor.setExtraSelections(self._error_selections); else: self._editor.setExtraSelections([]); self.emit(PYSIGNAL("hasErrors()"),self,nerr_local); def show_error (self,err_num,filename,line,column): """Shows error at the given position, but only if the filename matches. Can be directly connected to a showError() signal from an error window"""; if filename == self._filename: errnum,cursor = self._error_at_line.get(line-1,(None,None)); if cursor: # change selections self._current_error_selection.cursor = cursor; self._current_error_selection.format = self._format_current_error; sels = self._error_selections[:errnum] + self._error_selections[(errnum+1):]; sels.append(self._current_error_selection); self._editor.setExtraSelections(sels); # move current cursor cursor = self._editor.textCursor(); # scroll to line-1 and line+1 to make sure line is fully visible if line > 1: cursor.setPosition(self._find_block_by_linenumber(line-2).position() + column); self._editor.setTextCursor(cursor); self._editor.ensureCursorVisible(); if line < self._document.blockCount(): cursor.setPosition(self._find_block_by_linenumber(line).position() + column); self._editor.setTextCursor(cursor); self._editor.ensureCursorVisible(); # finally, scroll to line cursor.setPosition(self._find_block_by_linenumber(line).position() + column); self._editor.setTextCursor(cursor); self._editor.ensureCursorVisible(); def sync_external_file (self,filename=None,ask=True): # # NB: problem because it resets the errors filename = filename or self._filename; filetime = _file_mod_time(filename); if not filetime or filetime == self._file_disktime: return True; # in sync if not ask: res = QMessageBox.Discard; else: res = QMessageBox.warning(self,"TDL file changed", """<p><tt>%s</tt> has been modified by another program. Would you like to save your changes and overwrite the version on disk, discard your changes and revert to the version on disk, or cancel the operation?""" % (filename,),QMessageBox.Save|QMessageBox.Discard|QMessageBox.Cancel); if res == QMessageBox.Cancel: return None; elif res == QMessageBox.Discard: if filename != self._filename: self.load_file(filename,mainfile=self._mainfile); else: self.reload_file(); return True; # in sync def _save_file (self,filename=None,text=None,force=False,save_as=False): """Saves text. If force=False, checks modification times in case the file has been modified by another program. If force=True, saves unconditionally. If no filename is known, asks for one. Returns True if file was successfully saved, else None."""; filename = filename or self._filename; if filename and not save_as: if not force: if not self.sync_external_file(filename=filename,ask=True): return None; else: # no filename, ask for one try: dialog = self._save_as_dialog; dialog.setDirectory(dialog.directory()); # hopefully this rescan the directory except AttributeError: self._save_as_dialog = dialog = QFileDialog(self,"Saved TDL Script"); dialog.resize(800,dialog.height()); dialog.setMode(QFileDialog.AnyFile); dialog.setNameFilters(["TDL scripts (*.tdl *.py)","All files (*.*)"]); dialog.setViewMode(QFileDialog.Detail); if dialog.exec_() != QDialog.Accepted: return None; filename = str(dialog.selectedFiles()[0]); # save the file if text is None: text = str(self._editor.document().toPlainText()); try: outfile = file(filename,"w").write(text); except IOError: (exctype,excvalue,tb) = sys.exc_info(); _dprint(0,'exception',sys.exc_info(),'saving TDL file',filename); self.show_message("""<b>Error writing <tt>%s</tt>: <i>%s (%s)</i></b>""" % (filename,excvalue,exctype.__name__), error=True,transient=True); return None; # saved successfully, update stuff self._filename = filename; self._qa_revert.setEnabled(True); self._basename = os.path.basename(filename); self._file_disktime = _file_mod_time(filename); self._document.setModified(False); self.emit(PYSIGNAL("fileSaved()"),self,filename); return self._filename; def close (self): self._closed = True; def confirm_close (self): if self._document.isModified(): res = QMessageBox.warning(self,"TDL file modified", """Save modified file <p><tt>%s</tt>?</p>""" % (self._filename or "",),QMessageBox.Save|QMessageBox.Discard|QMessageBox.Cancel); if res == QMessageBox.Cancel: return False; if res == QMessageBox.Save: if not self._save_file(): return False; self.close(); return True; def _revert_to_saved (self,force=False): if not self._filename: return; if not force: if QMessageBox.question(self,"Revert to saved", """Revert to saved version of <p><tt>%s</tt>?"""%(self._filename,), QMessageBox.Ok|QMessageBox.Cancel) == QMessageBox.Cancel: return; self.load_file(self._filename,mainfile=self._mainfile); def _add_menu_label (self,menu,label): tlab = QLabel("<b>"+label+"</b>",menu); tlab.setAlignment(Qt.AlignCenter); tlab.setFrameShape(QFrame.ToolBarPanel); tlab.setFrameShadow(QFrame.Sunken); menu.insertItem(tlab); def has_compile_options (self): return self._options_menu.treeWidget().topLevelItemCount(); def has_runtime_options (self): return self._tdlexec_menu.treeWidget().topLevelItemCount(); def import_content (self,force=False,show_options=False): """imports TDL module but does not run _define_forest(). Depending on autosync/modified state, asks to save or revert. If module is already imported, does nothing, unless force=True, in which case it imports unconditionally. If do_compile=True, proceeds to show compile-time options on success, or to compile directly if there are no options Return value: True on successful import None if cancelled by user. Import errors are posted to the error dialog. """; # save list of publishers (since forest will be cleared on import) if self._pub_nodes is None: if TDL.Compile.last_imported_file() == self._filename: self._pub_nodes = [ node.name for node in meqds.nodelist.iternodes() if node.is_publishing() ]; _dprint(2,"last imported file matches, saving",len(self._pub_nodes),"publishers") else: self._pub_nodes = []; _dprint(2,"last imported file doesn't match:",TDL.Compile.last_imported_file(),self._filename) _dprint(1,self._filename,"importing"); self.clear_message(); self.clear_errors(); self._options_menu.hide(); self._tb_tdlexec.hide(); self._tdlexec_menu.hide(); # change the current directory to where the file is # os.chdir(os.path.dirname(self._filename)); # The Python imp module expects text to reside in a disk file, which is # a pain in the ass for us if we're dealing with modified text or text # entered on-the-fly. So, either save or sync before proceeding global _external_sync; if self._document.isModified() or not self._filename: if not self._save_file(): return None; else: if not self.sync_external_file(ask=False): return None; # if we already have an imported module and disk file hasn't changed, skip # the importing step busy = BusyIndicator(); if force or self._tdlmod is None or self._tdlmod_filetime == self._file_disktime: # reset data members _dprint(2,self._filename,"emitting signal for 0 compile-time options"); self.emit(PYSIGNAL("hasCompileOptions()"),self,0); self._options_menu.hide(); self._options_menu.clear(); self._tdlmod = None; # get text from editor tdltext = str(self._document.toPlainText()); try: tdlmod,tdltext = TDL.Compile.import_tdl_module(self._filename,tdltext); # catch import errors except TDL.CumulativeError,value: _dprint(0,"caught cumulative error, length",len(value.args)); self._error_window.set_errors(value.args,message="TDL import failed"); busy = None; return None; except Exception,value: _dprint(0,"caught other error, traceback follows"); traceback.print_exc(); self._error_window.set_errors([value],message="TDL import failed"); busy = None; return None; # remember module and nodescope self._tdlmod = tdlmod; self._tdltext = tdltext; self._tdlmod_filetime = self._file_disktime; # build options menu from compile-time options opt_tw = self._options_menu.treeWidget(); opts = TDLOptions.get_compile_options(); if opts: # add options try: TDLOptions.populate_option_treewidget(opt_tw,opts); except Exception,value: _dprint(0,"error setting up TDL options GUI"); traceback.print_exc(); self._error_window.set_errors([value],message="Error setting up TDL options GUI"); busy = None; return None; # self._tb_opts.show(); _dprint(2,self._filename,"emitting signal for",len(opts),"compile-time options"); self.emit(PYSIGNAL("hasCompileOptions()"),self,len(opts));
class TDLEditor(QFrame, PersistentCurrier): SupportsLineNumbers = False # a single editor always has the focus current_editor = None # flag: sync to external editor external_sync = True def set_external_sync(value): global _external_sync _external_sync = value set_external_sync = staticmethod(set_external_sync) def __init__(self, parent, close_button=False, error_window=None): QFrame.__init__(self, parent) self._enabled = True toplo = QVBoxLayout(self) toplo.setContentsMargins(0, 0, 0, 0) toplo.setSpacing(0) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) splitter = QSplitter(Qt.Vertical, self) toplo.addWidget(splitter) splitter.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) splitter.setChildrenCollapsible(False) # figure out our app_gui parent self._appgui = app_proxy_gui.appgui(parent) # create an editor box editor_box = QFrame(splitter) lo = QVBoxLayout(editor_box) lo.setContentsMargins(0, 0, 0, 0) lo.setSpacing(0) # find main window to associate our toolbar with mainwin = parent while mainwin and not isinstance(mainwin, QMainWindow): mainwin = mainwin.parent() self._toolbar = QToolBar(mainwin or self) lo.addWidget(self._toolbar) self._toolbar.setIconSize(QSize(16, 16)) #### populate toolbar # Exec button and menu self._tb_tdlexec = QToolButton(self._toolbar) self._toolbar.addWidget(self._tb_tdlexec) self._tb_tdlexec.setIcon(pixmaps.gear.icon()) self._tb_tdlexec.setText("Exec") self._tb_tdlexec.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self._tb_tdlexec.setToolTip( "Accesses run-time options & jobs defined by this TDL script") self._tb_tdlexec.hide() jobs = self._tdlexec_menu = TDLOptionsDialog(self) jobs.setWindowTitle("TDL Jobs & Runtime Options") jobs.setWindowIcon(pixmaps.gear.icon()) jobs.hide() QObject.connect(self._tb_tdlexec, SIGNAL("clicked()"), jobs.exec_) # save menu and button self._tb_save = QToolButton(self._toolbar) self._toolbar.addWidget(self._tb_save) self._tb_save.setIcon(pixmaps.file_save.icon()) self._tb_save.setToolTip( "Saves script. Click on the down-arrow for other options.") savemenu = QMenu(self) self._tb_save.setMenu(savemenu) self._tb_save.setPopupMode(QToolButton.MenuButtonPopup) self._tb_save._modified_color = QColor("yellow") qa_save = savemenu.addAction(pixmaps.file_save.icon(), "&Save script", self._save_file) qa_save.setShortcut(Qt.ALT + Qt.Key_S) QObject.connect(self._tb_save, SIGNAL("clicked()"), self._save_file) qa_save_as = savemenu.addAction( pixmaps.file_save.icon(), "Save script &as...", self.curry(self._save_file, save_as=True)) qa_revert = self._qa_revert = savemenu.addAction( "Revert to saved", self._revert_to_saved) # run menu and button self._qa_run = self._toolbar.addAction( pixmaps.blue_round_reload.icon(), "&Save & reload", self._import_main_file) self._qa_run.setShortcut(Qt.ALT + Qt.Key_R) # Compile-time options and menu self._tb_opts = QToolButton(self._toolbar) self._toolbar.addWidget(self._tb_opts) self._tb_opts.setIcon(pixmaps.wrench.icon()) self._tb_opts.setText("Options") self._tb_opts.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self._tb_opts.setToolTip( "Accesses compile-time options for this TDL script") # self._tb_opts.hide(); opts = self._options_menu = TDLOptionsDialog( self, ok_label="Compile", ok_icon=pixmaps.blue_round_reload) opts.setWindowTitle("TDL Compile-time Options") opts.setWindowIcon(pixmaps.wrench.icon()) QObject.connect(opts, PYSIGNAL("accepted()"), self._compile_main_file) QObject.connect(TDLOptions.OptionObject, SIGNAL("mandatoryOptionsSet"), self.mark_mandatory_options_set) opts.hide() QObject.connect(self._tb_opts, SIGNAL("clicked()"), opts.show) # cross-connect the rereshProfiles() signals, so that dialogs can update each other's # profile menus QObject.connect(self._options_menu, PYSIGNAL("refreshProfiles()"), self._tdlexec_menu.refresh_profiles) QObject.connect(self._tdlexec_menu, PYSIGNAL("refreshProfiles()"), self._options_menu.refresh_profiles) self._toolbar.addSeparator() # cursor position indicator self._poslabel = QLabel(self._toolbar) #wa = QWidgetAction(self._toolbar); #wa.setDefaultWidget(self._poslabel); #self._toolbar.addAction(wa); #self._toolbar.addWidget(self._poslabel); self._toolbar.addWidget(self._poslabel) width = self._poslabel.fontMetrics().width("L:999 C:999") self._poslabel.setMinimumWidth(width) self._poslabel.setText("L:0 C:0") # filename indicator self._pathlabel = QLabel(self._toolbar) #wa = QWidgetAction(self._toolbar); #wa.setDefaultWidget(self._pathlabel); self._toolbar.addWidget(self._pathlabel) #self._toolbar.addAction(wa); self._pathlabel.show() self._pathlabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self._pathlabel.setIndent(10) self._pathlabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) if close_button: if not isinstance(close_button, QIcon): close_button = pixmaps.remove.icon() self._qa_close = self._toolbar.addAction(close_button, "&Close file", self._file_closed) self._qa_close.setShortcut(Qt.ALT + Qt.Key_W) self._pathlabel.setText("(no file)") #### add editor window self._editor = editor = QTextEdit(editor_box) lo.addWidget(self._editor) editor.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) editor.setAcceptRichText(False) editor.setLineWrapMode(QTextEdit.NoWrap) self._document = QTextDocument(self) editor.setDocument(self._document) QObject.connect(self._document, SIGNAL("modificationChanged(bool)"), self._text_modified) QObject.connect(self._editor, SIGNAL("cursorPositionChanged()"), self._display_cursor_position) # QObject.connect(self._editor,SIGNAL("textChanged()"),self._clear_transients); # add character formats for error display self._format_error = QTextCharFormat(self._editor.currentCharFormat()) self._format_error.setBackground(QBrush(QColor("lightpink"))) self._format_suberror = QTextCharFormat( self._editor.currentCharFormat()) self._format_suberror.setBackground(QBrush(QColor("lightgrey"))) self._format_current_error = QTextCharFormat( self._editor.currentCharFormat()) self._format_current_error.setBackground(QBrush(QColor("orangered"))) # add message window self._message_box = QFrame(editor_box) lo.addWidget(self._message_box) self._message_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self._message_box.setFrameStyle(QFrame.Panel + QFrame.Sunken) self._message_box.setLineWidth(2) mblo = QVBoxLayout(self._message_box) mblo.setContentsMargins(0, 0, 0, 0) msgb1 = QHBoxLayout() mblo.addLayout(msgb1) msgb1.setContentsMargins(0, 0, 0, 0) msgb1.setSpacing(0) self._message_icon = QToolButton(self._message_box) msgb1.addWidget(self._message_icon) self._message = QLabel(self._message_box) msgb1.addWidget(self._message) self._message.setTextFormat(Qt.RichText) self._message.setWordWrap(True) self._message.setMargin(0) self._message.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) # self._message_icon.setAlignment(Qt.AlignTop); # self._message_icon.setMargin(4); self._message_icon.setAutoRaise(True) self._message_icon.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) QObject.connect(self._message_icon, SIGNAL("clicked()"), self.clear_message) self._message_icon.setToolTip("Click here to clear the message") self._message_widgets = [] self._message_transient = False # figure out if we already have an error box to attach to self._error_window = error_window or getattr( parent, '_tdlgui_error_window', None) if self._error_window: #self._resize_errwin = False; pass else: # otherwise create an error floater self._error_window = TDLErrorFloat(parent) setattr(parent, '_tdlgui_error_window', self._error_window) # self._resize_errwin = True; QObject.connect(self._error_window, PYSIGNAL("hasErrors()"), self._reset_errors) QObject.connect(self._error_window, PYSIGNAL("showError()"), self.show_error) # set filename self._filename = None # "official" path of file (None if new script not yet saved) self._mainfile = None # if not None, then we're "slaved" to a main file (see below) self._file_disktime = None # modtime on disk when file was loaded self._basename = None self._modified = False self._closed = False self._error_at_line = {} self._is_tree_in_sync = True self._pub_nodes = None def __del__(self): self.has_focus(False) def disable_editor(self): """Called before disabling the editor, as on some versions of PyQt the object is not destroyed properly and keeps receving signals""" self._enabled = False QObject.disconnect(TDLOptions.OptionObject, SIGNAL("mandatoryOptionsSet"), self.mark_mandatory_options_set) def show_compile_options(self): self._options_menu.show() def mark_mandatory_options_set(self, enabled): self._options_menu.enableOkButton(enabled) def show_runtime_options(self): self._tdlexec_menu.show() def tree_is_in_sync(self, sync=True): """Tells the editor wheether the current tree is in sync with the content of the script. This is indicated by a visual cue on the toolbar. """ if sync: self._tb_tdlexec.setIcon(pixmaps.gear.icon()) self._tb_tdlexec.setToolTip( "Access run-time options & jobs defined by this TDL script") else: self._tb_tdlexec.setIcon(pixmaps.exclaim_yellow_warning.icon()) self._tb_tdlexec.setToolTip( """Access run-time options & jobs defined by this TDL script. Warning! You have modified the script since it was last compiled, so the tree may be out of date.""" ) def _file_closed(self): self.emit(PYSIGNAL("fileClosed()"), self) def hideEvent(self, ev): self.emit(PYSIGNAL("hidden()")) self.emit(PYSIGNAL("visible()"), False) return QFrame.hideEvent(self, ev) def showEvent(self, ev): self.emit(PYSIGNAL("shown()")) self.emit(PYSIGNAL("visible()"), True) return QFrame.showEvent(self, ev) def hide_jobs_menu(self, dum=False): if self._closed: return self._tb_tdlexec.hide() self.clear_message() def show_line_numbers(self, show): pass def show_run_control(self, show=True): if self._closed: return self._qa_run.setVisible(show) def enable_controls(self, enable=True): if self._closed: return self._qa_run.setEnabled(enable) self._tb_tdlexec.setEnabled(enable) if not enable: self.clear_message() def disable_controls(self, disable=True): if self._closed: return self._qa_run.setDisabled(disable) self._tb_tdlexec.setDisabled(disable) if disable: self.clear_message() def get_filename(self): return self._filename def get_mainfile(self): return self._mainfile def _import_main_file(self): # self._tb_opts.setOn(False); self.clear_errors() if self._document.isModified(): self._save_file() self.emit(PYSIGNAL("importFile()"), self, self._mainfile or self._filename) def _compile_main_file(self): # self._tb_opts.setOn(False); self.clear_errors() if self._document.isModified(): self._save_file() self.emit(PYSIGNAL("compileFile()"), self, self._mainfile or self._filename) def _clear_transients(self): """if message box contains a transient message, clears it""" if self._message_transient: self.clear_message() def _display_cursor_position(self): cursor = self._editor.textCursor() pos = cursor.position() col = cursor.columnNumber() line = cursor.blockNumber() self._poslabel.setText("L:<b>%d</b> C:<b>%d</b>" % (line + 1, col + 1)) self._poslabel.repaint() def _text_modified(self, mod=True): self._modified = mod self.emit(PYSIGNAL("textModified()"), self, bool(mod)) self._tb_save.setAutoRaise(not mod) self._qa_revert.setEnabled(mod) #if mod: #self._tb_save.setBackgroundRole(QPalette.ToolTipBase); #else: #self._tb_save.setBackgroundRole(QPalette.Button); if self._filename: label = '<b>' + self._basename + '</b>' self._pathlabel.setToolTip(self._filename) else: label = '' self._pathlabel.setToolTip('') if self._readonly: label = '[r/o] ' + label if mod: self._clear_transients() label = '[mod] ' + label self._pathlabel.setText(label) def clear_message(self): # traceback.print_stack(); self._message_box.hide() self._message.setText('') self._message_icon.setText('') self._message_transient = False if self._message_widgets: dum = QWidget() for w in self._message_widgets: w.reparent(dum) self._message_widgets = [] def show_message(self, msg, error=False, icon=None, transient=False): """Shows message in box. If icon is not None, overrides standard icon. If error=True, uses error icon (icon overrides this). If transient=True, message will be cleared when text is edited. """ self._message.setText(msg) if not icon: if error: icon = pixmaps.red_round_cross.icon() else: icon = pixmaps.info_blue_round.icon() self._message_icon.setIcon(icon) self._message_box.show() self._message_transient = transient self.clear_errors() def messagebox(): return self._message_box def add_message_widget(self, widget): self._mblo.addWidget(widget) self._message_widgets.append(widget) def clear_errors(self): self._error_window.clear_errors(emit_signal=True) def _find_block_by_linenumber(self, line): block = block0 = self._document.begin() nblock = 1 while block and nblock < line: block0 = block block = block0.next() nblock += 1 return block or block0 def _reset_errors(self, nerr): """helper method, resets error markers and such. Usually tied to a hasErrors() signal from an error window""" if not self._enabled: return self._error_at_line = {} self._error_selections = [] self._current_error_selection = QTextEdit.ExtraSelection() nerr_local = 0 if nerr: error_locations = self._error_window.get_error_locations() for err_num, filename, line, column, suberror in error_locations: if filename == self._filename: nerr_local += 1 # make cursor corresponding to line containing the error cursor = QTextCursor(self._find_block_by_linenumber(line)) cursor.select(QTextCursor.LineUnderCursor) # make selection object qsel = QTextEdit.ExtraSelection() qsel.cursor = cursor if suberror: qsel.format = self._format_suberror else: qsel.format = self._format_error # insert into error_at_line list self._error_at_line[line - 1] = len( self._error_selections), cursor # append to list of error selections self._error_selections.append(qsel) self._editor.setExtraSelections(self._error_selections) else: self._editor.setExtraSelections([]) self.emit(PYSIGNAL("hasErrors()"), self, nerr_local) def show_error(self, err_num, filename, line, column): """Shows error at the given position, but only if the filename matches. Can be directly connected to a showError() signal from an error window""" if filename == self._filename: errnum, cursor = self._error_at_line.get(line - 1, (None, None)) if cursor: # change selections self._current_error_selection.cursor = cursor self._current_error_selection.format = self._format_current_error sels = self._error_selections[:errnum] + self._error_selections[ (errnum + 1):] sels.append(self._current_error_selection) self._editor.setExtraSelections(sels) # move current cursor cursor = self._editor.textCursor() # scroll to line-1 and line+1 to make sure line is fully visible if line > 1: cursor.setPosition( self._find_block_by_linenumber(line - 2).position() + column) self._editor.setTextCursor(cursor) self._editor.ensureCursorVisible() if line < self._document.blockCount(): cursor.setPosition( self._find_block_by_linenumber(line).position() + column) self._editor.setTextCursor(cursor) self._editor.ensureCursorVisible() # finally, scroll to line cursor.setPosition( self._find_block_by_linenumber(line).position() + column) self._editor.setTextCursor(cursor) self._editor.ensureCursorVisible() def sync_external_file(self, filename=None, ask=True): # # NB: problem because it resets the errors filename = filename or self._filename filetime = _file_mod_time(filename) if not filetime or filetime == self._file_disktime: return True # in sync if not ask: res = QMessageBox.Discard else: res = QMessageBox.warning( self, "TDL file changed", """<p><tt>%s</tt> has been modified by another program. Would you like to save your changes and overwrite the version on disk, discard your changes and revert to the version on disk, or cancel the operation?""" % (filename, ), QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if res == QMessageBox.Cancel: return None elif res == QMessageBox.Discard: if filename != self._filename: self.load_file(filename, mainfile=self._mainfile) else: self.reload_file() return True # in sync def _save_file(self, filename=None, text=None, force=False, save_as=False): """Saves text. If force=False, checks modification times in case the file has been modified by another program. If force=True, saves unconditionally. If no filename is known, asks for one. Returns True if file was successfully saved, else None.""" filename = filename or self._filename if filename and not save_as: if not force: if not self.sync_external_file(filename=filename, ask=True): return None else: # no filename, ask for one try: dialog = self._save_as_dialog dialog.setDirectory(dialog.directory()) # hopefully this rescan the directory except AttributeError: self._save_as_dialog = dialog = QFileDialog( self, "Saved TDL Script") dialog.resize(800, dialog.height()) dialog.setMode(QFileDialog.AnyFile) dialog.setNameFilters( ["TDL scripts (*.tdl *.py)", "All files (*.*)"]) dialog.setViewMode(QFileDialog.Detail) if dialog.exec_() != QDialog.Accepted: return None filename = str(dialog.selectedFiles()[0]) # save the file if text is None: text = str(self._editor.document().toPlainText()) try: outfile = file(filename, "w").write(text) except IOError: (exctype, excvalue, tb) = sys.exc_info() _dprint(0, 'exception', sys.exc_info(), 'saving TDL file', filename) self.show_message("""<b>Error writing <tt>%s</tt>: <i>%s (%s)</i></b>""" % (filename, excvalue, exctype.__name__), error=True, transient=True) return None # saved successfully, update stuff self._filename = filename self._qa_revert.setEnabled(True) self._basename = os.path.basename(filename) self._file_disktime = _file_mod_time(filename) self._document.setModified(False) self.emit(PYSIGNAL("fileSaved()"), self, filename) return self._filename def close(self): self._closed = True def confirm_close(self): if self._document.isModified(): res = QMessageBox.warning( self, "TDL file modified", """Save modified file <p><tt>%s</tt>?</p>""" % (self._filename or "", ), QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if res == QMessageBox.Cancel: return False if res == QMessageBox.Save: if not self._save_file(): return False self.close() return True def _revert_to_saved(self, force=False): if not self._filename: return if not force: if QMessageBox.question( self, "Revert to saved", """Revert to saved version of <p><tt>%s</tt>?""" % (self._filename, ), QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Cancel: return self.load_file(self._filename, mainfile=self._mainfile) def _add_menu_label(self, menu, label): tlab = QLabel("<b>" + label + "</b>", menu) tlab.setAlignment(Qt.AlignCenter) tlab.setFrameShape(QFrame.ToolBarPanel) tlab.setFrameShadow(QFrame.Sunken) menu.insertItem(tlab) def has_compile_options(self): return self._options_menu.treeWidget().topLevelItemCount() def has_runtime_options(self): return self._tdlexec_menu.treeWidget().topLevelItemCount() def import_content(self, force=False, show_options=False): """imports TDL module but does not run _define_forest(). Depending on autosync/modified state, asks to save or revert. If module is already imported, does nothing, unless force=True, in which case it imports unconditionally. If do_compile=True, proceeds to show compile-time options on success, or to compile directly if there are no options Return value: True on successful import None if cancelled by user. Import errors are posted to the error dialog. """ # save list of publishers (since forest will be cleared on import) if self._pub_nodes is None: if TDL.Compile.last_imported_file() == self._filename: self._pub_nodes = [ node.name for node in meqds.nodelist.iternodes() if node.is_publishing() ] _dprint(2, "last imported file matches, saving", len(self._pub_nodes), "publishers") else: self._pub_nodes = [] _dprint(2, "last imported file doesn't match:", TDL.Compile.last_imported_file(), self._filename) _dprint(1, self._filename, "importing") self.clear_message() self.clear_errors() self._options_menu.hide() self._tb_tdlexec.hide() self._tdlexec_menu.hide() # change the current directory to where the file is # os.chdir(os.path.dirname(self._filename)); # The Python imp module expects text to reside in a disk file, which is # a pain in the ass for us if we're dealing with modified text or text # entered on-the-fly. So, either save or sync before proceeding global _external_sync if self._document.isModified() or not self._filename: if not self._save_file(): return None else: if not self.sync_external_file(ask=False): return None # if we already have an imported module and disk file hasn't changed, skip # the importing step busy = BusyIndicator() if force or self._tdlmod is None or self._tdlmod_filetime == self._file_disktime: # reset data members _dprint(2, self._filename, "emitting signal for 0 compile-time options") self.emit(PYSIGNAL("hasCompileOptions()"), self, 0) self._options_menu.hide() self._options_menu.clear() self._tdlmod = None # get text from editor tdltext = str(self._document.toPlainText()) try: tdlmod, tdltext = TDL.Compile.import_tdl_module( self._filename, tdltext) # catch import errors except TDL.CumulativeError, value: _dprint(0, "caught cumulative error, length", len(value.args)) self._error_window.set_errors(value.args, message="TDL import failed") busy = None return None except Exception, value: _dprint(0, "caught other error, traceback follows") traceback.print_exc() self._error_window.set_errors([value], message="TDL import failed") busy = None return None # remember module and nodescope self._tdlmod = tdlmod self._tdltext = tdltext self._tdlmod_filetime = self._file_disktime # build options menu from compile-time options opt_tw = self._options_menu.treeWidget() opts = TDLOptions.get_compile_options() if opts: # add options try: TDLOptions.populate_option_treewidget(opt_tw, opts) except Exception, value: _dprint(0, "error setting up TDL options GUI") traceback.print_exc() self._error_window.set_errors( [value], message="Error setting up TDL options GUI") busy = None return None # self._tb_opts.show(); _dprint(2, self._filename, "emitting signal for", len(opts), "compile-time options") self.emit(PYSIGNAL("hasCompileOptions()"), self, len(opts))