def __init__(self, fileName, interface, mapFiles): self.fileName = fileName self.editTitle = self.fileName if os.getenv("TEXTTEST_HOME"): self.editTitle = self.fileName.replace(os.getenv("TEXTTEST_HOME") + "/", "") self.interface = interface self.uiMapFileHandler = UIMapFileHandler(mapFiles) self.scriptEngine = EditorScriptEngine(uiMapFiles=[]) self.initShortcutManager() self.allEntries = OrderedDict() self.allDescriptionWidgets = [] self.popupSensitivities = {} self.createdShortcuts = []
def __init__(self, fileName, interface, mapFiles): self.fileName = fileName self.interface = interface self.uiMapFileHandler = UIMapFileHandler(mapFiles) self.scriptEngine = gtktoolkit.ScriptEngine(uiMapFiles=[]) self.allEntries = OrderedDict()
class UseCaseNameChooser: title = "Enter Usecase names for auto-recorded actions" def __init__(self, fileName, interface, mapFiles): self.fileName = fileName self.interface = interface self.uiMapFileHandler = UIMapFileHandler(mapFiles) self.scriptEngine = gtktoolkit.ScriptEngine(uiMapFiles=[]) self.allEntries = OrderedDict() def collectNames(self): commands = [ line.strip() for line in open(self.fileName) ] autoGenerated = self.getAutoGenerated(commands) if len(autoGenerated) == 0: return autoGeneratedInfo = self.parseAutoGenerated(autoGenerated) dialog = self.createDialog(autoGeneratedInfo, commands) response = dialog.run() if response == gtk.RESPONSE_ACCEPT: newNames = [ entry.get_text() for entry in self.allEntries.values() ] dialog.destroy() self.replaceInFile(self.fileName, zip(autoGenerated, newNames)) toStore = zip(autoGeneratedInfo, newNames) for ((command, widgetType, widgetDescription, signalName), eventName) in toStore: self.uiMapFileHandler.storeInfo(widgetDescription, signalName, eventName) self.uiMapFileHandler.write() else: # Don't leave a half generated filename behind, if we didn't fill in the dialog properly # we should remove it so nobody saves it... os.remove(self.fileName) dialog.destroy() def getAutoGenerated(self, commands): # Find the auto-generated commands and strip them of their arguments autoGenerated = [] for command in commands: if command.startswith("Auto."): pos = command.rfind("'") commandWithoutArg = command[:pos + 1] if not commandWithoutArg in autoGenerated: autoGenerated.append(commandWithoutArg) return autoGenerated def parseAutoGenerated(self, commands): autoGenerated = [] for command in commands: parts = command[5:].split("'") initialPart = parts[0][:-1] widgetType, signalName = initialPart.split(".", 1) widgetDescription = parts[1].replace("<APOSTROPHE>", "'") autoGenerated.append((command, widgetType, widgetDescription, signalName)) return autoGenerated def replaceInFile(self, fileName, replacements): newFileName = fileName + ".tmp" newFile = open(newFileName, "w") for line in open(fileName): newLine = self.makeReplacement(line, replacements) if newLine: newFile.write(newLine) newFile.close() shutil.move(newFileName, fileName) def makeReplacement(self, command, replacements): for origName, newName in replacements: if command.startswith(origName): if newName: return command.replace(origName, newName) else: return return command def createDialog(self, autoGenerated, commands): dialog = gtk.Dialog(self.title, flags=gtk.DIALOG_MODAL) dialog.set_name("Name Entry Window") contents = self.createTable(autoGenerated, dialog) dialog.vbox.pack_start(contents, expand=True, fill=True) preview = self.createPreview(commands) dialog.vbox.pack_start(gtk.HSeparator()) dialog.vbox.pack_start(preview, expand=True, fill=True) yesButton = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) cancelButton = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.set_default_response(gtk.RESPONSE_ACCEPT) self.scriptEngine.monitorSignal("finish name entry editing", "clicked", yesButton) self.scriptEngine.monitorSignal("cancel name entry editing", "clicked", cancelButton) dialog.show_all() return dialog def createMarkupLabel(self, markup): label = gtk.Label() label.set_markup(markup) return label def activateEntry(self, entry, dialog, *args): dialog.response(gtk.RESPONSE_ACCEPT) def getActionDescription(self, signalName, widgetType): try: exec "from " + self.interface + "toolkit import ScriptEngine" except ImportError: # If we haven't even got any such interface, don't worry about this mechanism return signalName desc = ScriptEngine.getDisplayName(signalName) if desc: return desc if signalName == "activate": if "Entry" in widgetType: return "pressed Enter" else: return "selected" elif signalName == "changed": if "Entry" in widgetType: return "edited text" else: return "selected item" parts = signalName.split(".") if len(parts) == 1: return signalName.replace("-", " ") if parts[0] == "response": text = parts[1] if "--" in text: return text.replace("--", "='") + "'" else: return text columnName = parts[1] remaining = parts[0] if remaining == "toggled": remaining = ".".join([ remaining, parts[-1] ]) return ScriptEngine.getColumnDisplayName(remaining) + " '" + columnName + "'" def splitAutoCommand(self, command): for cmd in self.allEntries.keys(): if command.startswith(cmd): arg = command.replace(cmd, "") return cmd, arg return None, None def updatePreview(self, entry, data): buffer, lineNo, arg = data text = entry.get_text() or "?" toUse = self.convertToUtf8(text + arg) start = buffer.get_iter_at_line(lineNo) end = buffer.get_iter_at_line(lineNo + 1) buffer.delete(start, end) buffer.insert(start, toUse + "\n") def createPreview(self, commands): frame = gtk.Frame("Current Usecase Preview") view = gtk.TextView() view.set_editable(False) view.set_cursor_visible(False) view.set_wrap_mode(gtk.WRAP_WORD) buffer = view.get_buffer() for ix, command in enumerate(commands): autoCmdName, autoArg = self.splitAutoCommand(command) if autoCmdName: buffer.insert(buffer.get_end_iter(), self.convertToUtf8("?" + autoArg + "\n")) entry = self.allEntries.get(autoCmdName) entry.connect("changed", self.updatePreview, (buffer, ix, autoArg)) else: buffer.insert(buffer.get_end_iter(), self.convertToUtf8(command) + "\n") frame.add(view) return frame def convertToUtf8(self, text): try: localeEncoding = getdefaultlocale()[1] if localeEncoding: return unicode(text, localeEncoding, errors="replace").encode('utf-8', 'replace') except ValueError: pass return text def createTable(self, autoGenerated, dialog): table = gtk.Table(rows=len(autoGenerated) + 1, columns=4) table.set_col_spacings(20) headers = [ "Widget Type", "Identified By", "Action Performed", "Usecase Name" ] for col, header in enumerate(headers): table.attach(self.createMarkupLabel("<b><u>" + header + "</u></b>"), col, col + 1, 0, 1, xoptions=gtk.FILL) for rowIndex, (command, widgetType, widgetDesc, signalName) in enumerate(autoGenerated): table.attach(gtk.Label(widgetType), 0, 1, rowIndex + 1, rowIndex + 2, xoptions=gtk.FILL) actionDesc = self.getActionDescription(signalName, widgetType) widgetDescUtf8 = self.convertToUtf8(widgetDesc) table.attach(gtk.Label(widgetDescUtf8), 1, 2, rowIndex + 1, rowIndex + 2, xoptions=gtk.FILL) table.attach(gtk.Label(actionDesc), 2, 3, rowIndex + 1, rowIndex + 2, xoptions=gtk.FILL) entry = gtk.Entry() scriptName = "enter usecase name for signal '" + signalName + "' on " + widgetType + " '" + widgetDesc + "' =" self.scriptEngine.monitorSignal(scriptName, "changed", entry) entry.connect("activate", self.activateEntry, dialog) self.scriptEngine.monitorSignal("finish name entry editing by pressing <enter>", "activate", entry) self.allEntries[command] = entry table.attach(entry, 3, 4, rowIndex + 1, rowIndex + 2) frame = gtk.Frame("Previously unseen actions: provide names for the interesting ones") frame.add(table) return frame
class UseCaseEditor: enterTitle = "Enter Usecase names for auto-recorded actions" def __init__(self, fileName, interface, mapFiles): self.fileName = fileName self.editTitle = self.fileName if os.getenv("TEXTTEST_HOME"): self.editTitle = self.fileName.replace(os.getenv("TEXTTEST_HOME") + "/", "") self.interface = interface self.uiMapFileHandler = UIMapFileHandler(mapFiles) self.scriptEngine = EditorScriptEngine(uiMapFiles=[]) self.initShortcutManager() self.allEntries = OrderedDict() self.allDescriptionWidgets = [] self.popupSensitivities = {} self.createdShortcuts = [] def initShortcutManager(self): self.shortcutManager = ShortcutManager() for shortcut in gtktoolkit.ScriptEngine.getShortcuts(): self.shortcutManager.add(shortcut) def getAllCommands(self): return [ line.strip() for line in encodingutils.openEncoded(self.fileName) ] def getNewUsecaseNames(self): return [ entry.get_text() for entry in self.allEntries.values() ] def getNewWidgetDescriptions(self): def get_text(widget): return removeMarkup(unescape(widget.get_active_text())) if hasattr(widget, "get_active_text") else widget.get_text() return map(get_text, self.allDescriptionWidgets) def run(self): commands = self.getAllCommands() autoGenerated = self.getAutoGenerated(commands) autoGeneratedInfo = self.parseAutoGenerated(autoGenerated) self.isAutoGenerated = len(autoGenerated) > 0 dialog = self.createDialog(autoGeneratedInfo, commands) self.runDialog(dialog, autoGenerated, autoGeneratedInfo) def runDialog(self, dialog, autoGenerated, autoGeneratedInfo): response = dialog.run() if response == gtk.RESPONSE_ACCEPT: newNames = self.getNewUsecaseNames() if len(autoGenerated) > 0: duplicateNames = self.getDuplicateNames(newNames) if duplicateNames and not self.acceptDuplicateNames(dialog, duplicateNames): self.runDialog(dialog, autoGenerated, autoGeneratedInfo) dialog.destroy() self.replaceInFile(self.fileName, self.makeReplacement, zip(autoGenerated, newNames)) toStore = zip(self.getNewWidgetDescriptions(), autoGeneratedInfo.values(), newNames) for widgetDescription, signalInfo, eventName in toStore: self.uiMapFileHandler.storeInfo(widgetDescription, signalInfo[-1], eventName) self.uiMapFileHandler.write() elif len(autoGenerated) == 0: dialog.destroy() else: # Don't leave a half generated filename behind, if we didn't fill in the dialog properly # we should remove it so nobody saves it... for shortcutName in self.createdShortcuts: os.remove(shortcutName) os.remove(self.fileName) dialog.destroy() def getDuplicateNames(self, allNames): duplicates = [] for name in allNames: value, args = self.uiMapFileHandler.splitOptionValue(name) if name and value and not args: duplicates.append(name) return duplicates def acceptDuplicateNames(self, parent, duplicateNames): message = "You have entered a name that already exists in the UI map.\n" + \ "You are allowed to do this but please be aware it may cause problems.\n" + \ "The following duplicate names were found:\n" for duplicate in duplicateNames: message += duplicate + "\n" dialog = self.createMessageDialog(parent, message, gtk.MESSAGE_WARNING, "Warning", gtk.BUTTONS_OK_CANCEL) dialog.show_all() response = dialog.run() dialog.hide() return response == gtk.RESPONSE_OK def getAutoGenerated(self, commands): # Find the auto-generated commands and strip them of their arguments autoGenerated = [] for command in commands: if command.startswith("Auto."): pos = command.rfind("'") commandWithoutArg = command[:pos + 1] if not commandWithoutArg in autoGenerated: autoGenerated.append(commandWithoutArg) return autoGenerated def parseAutoGenerated(self, commands): autoGenerated = OrderedDict() for command in commands: parts = command[5:].split("'") initialPart = parts[0][:-1] widgetType, signalName = initialPart.split(".", 1) widgetDescription = self.uiMapFileHandler.unescape(parts[1]) autoGenerated[command] = widgetType, widgetDescription, signalName return autoGenerated def replaceInFile(self, fileName, replaceMethod, *args): newFileName = fileName + ".tmp" newFile = encodingutils.openEncoded(newFileName, "w") for i, line in enumerate(encodingutils.openEncoded(fileName)): newLine = replaceMethod(line, i, *args) if newLine: newFile.write(newLine) newFile.close() shutil.move(newFileName, fileName) def makeReplacement(self, command, position, replacements): for origName, newName in replacements: if command.startswith(origName): if newName: return command.replace(origName, newName) else: return return command def createDialog(self, autoGenerated, commands): title = self.enterTitle if len(autoGenerated) > 0 else self.editTitle dialog = gtk.Dialog(title, flags=gtk.DIALOG_MODAL) dialog.set_name("Name Entry Window") height = int(gtk.gdk.screen_height() * 0.6) if len(autoGenerated) > 0: contents = self.createTable(autoGenerated, dialog) dialog.vbox.pack_start(contents, expand=True, fill=True) dialog.vbox.pack_start(gtk.HSeparator(), expand=False, fill=False) dialog.set_default_size(-1, height) else: width = min(int(gtk.gdk.screen_width() * 0.2), 500) dialog.set_default_size(width, height) preview = self.createPreview(commands, autoGenerated) dialog.vbox.pack_start(preview, expand=True, fill=True) yesButton = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) self.scriptEngine.monitorSignal("finish name entry editing", "clicked", yesButton) self.scriptEngine.monitorSignal("close editor window", "delete-event", dialog) if len(autoGenerated) > 0: cancelButton = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) self.scriptEngine.monitorSignal("cancel name entry editing", "clicked", cancelButton) dialog.set_default_response(gtk.RESPONSE_ACCEPT) dialog.show_all() return dialog def addScrollBar(self, widget, viewport=False): window = gtk.ScrolledWindow() window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) if viewport: window.add_with_viewport(widget) else: window.add(widget) return window def createMarkupLabel(self, markup): label = gtk.Label() label.set_markup(markup) return label def activateEntry(self, entry, dialog, *args): entryPos = self.allEntries.values().index(entry) if entryPos == len(self.allEntries) - 1: dialog.response(gtk.RESPONSE_ACCEPT) else: nextEntry = self.allEntries.values()[entryPos + 1] nextEntry.grab_focus() def getActionDescription(self, signalName, widgetType): try: exec "from " + self.interface + "toolkit import ScriptEngine" except ImportError: # If we haven't even got any such interface, don't worry about this mechanism return signalName desc = ScriptEngine.getDisplayName(signalName) #@UndefinedVariable if desc: return desc if signalName == "activate": if "Entry" in widgetType: return "pressed Enter" else: return "selected" elif signalName == "changed": if "Entry" in widgetType: return "edited text" else: return "selected item" parts = signalName.split(".") if len(parts) == 1: return signalName.replace("-", " ") if parts[0] == "response": text = parts[1] if "--" in text: return text.replace("--", "='") + "'" else: return text columnName = parts[1] remaining = parts[0] if remaining == "toggled": remaining = ".".join([ remaining, parts[-1] ]) return ScriptEngine.getColumnDisplayName(remaining) + " '" + columnName + "'" #@UndefinedVariable def splitAutoCommand(self, command, autoGenerated): for cmd in autoGenerated.keys(): if command.startswith(cmd): arg = command.replace(cmd, "").strip() return cmd, arg return None, None def addArgumentMarkup(self, arg): return "<i>" + escape(arg) + "</i>" if arg else "" def updatePreview(self, entry, data): model, iter = data text = entry.get_text() or "?" args = model.get_value(iter, 2) arg = " " + args[0] if args else "" markupFullText = self.convertToMarkup(text + self.addArgumentMarkup(arg)) fullText = text + arg model.set_value(iter, 0, markupFullText) model.set_value(iter, 1, fullText) def addText(self, model, rootIter, text, originalText, arguments, followIter=None): return model.insert_before(rootIter, followIter, [self.convertToMarkup(text), originalText, arguments]) def addCommandToModel(self, command, model, rootIter=None): shortcut, args = self.shortcutManager.findShortcut(command) if shortcut: self.addShortcutCommandToModel(shortcut, args, model, rootIter) else: self.addBasicCommandToModel(command, model, rootIter) def addShortcutCommandToModel(self, shortcut, args, model, rootIter, followIter=None): italicArgs = [ "<i>" + escape(arg) + "</i>" for arg in args ] text = "<b>" + shortcut.getShortcutNameWithArgs(italicArgs) + "</b>" iter = self.addText(model, rootIter, text, shortcut.getShortcutNameWithArgs(args), args, followIter) if not followIter: for step in shortcut.commands: self.addCommandToModel(shortcut.replaceArgs(step, args), model, iter) return iter def extractArgsAddMarkup(self, text, cmd): markup = text.replace(cmd, cmd + "<i>", 1) + "</i>" arg = text.replace(cmd, "", 1).strip() args = [ arg ] if arg else [] return markup, args def getErrorColouredText(self, text): return '<span foreground="red">' + text + "</span>" def addBasicCommandToModel(self, command, model, rootIter): args = [] if command.startswith(waitCommandName): markup, _ = self.extractArgsAddMarkup(escape(command), waitCommandName) # Ignore args for wait commands, they don't have anything in common text = '<span foreground="#826200">' + markup + "</span>" widgetDetails = [] else: widgetDetails = self.uiMapFileHandler.findSectionsAndOptions(command) text = escape(command) if widgetDetails: widgetDesc, signalName = widgetDetails[0] cmd = self.uiMapFileHandler.get(widgetDesc, signalName) if cmd != text: text, args = self.extractArgsAddMarkup(text, cmd) else: text = self.getErrorColouredText(text) iter = self.addText(model, rootIter, text, command, args) for widgetDesc, signalName in widgetDetails: msg = self.makeUIMapMessage(signalName, widgetDesc) self.addText(model, iter, msg, signalName, [widgetDesc]) def makeUIMapMessage(self, signalName, widgetDesc): return "Perform '" + signalName + "' on widget identified by '" + escape(widgetDesc) + "'" def createPreview(self, commands, autoGenerated): self.treeModel = gtk.TreeStore(str, str, object) view = gtk.TreeView(self.treeModel) view.set_headers_visible(False) cell = gtk.CellRendererText() column = gtk.TreeViewColumn("", cell, markup=0) view.append_column(column) view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.popup = self.createPopupMenu(view) for command in commands: autoCmdName, autoArg = self.splitAutoCommand(command, autoGenerated) if autoCmdName: args = [ autoArg ] if autoArg else [] autoArgMarkup = self.addArgumentMarkup(autoArg) text = "? " + autoArgMarkup if autoArgMarkup else "?" iter = self.addText(self.treeModel, None, text, None, args) entry = self.allEntries.get(autoCmdName) entry.connect("changed", self.updatePreview, (self.treeModel, iter)) widgetType, widgetDesc, signalName = autoGenerated.get(autoCmdName) msg = "Perform '" + signalName + "' on widget of type '" + widgetType + "' identified by '" + widgetDesc + "'" self.addText(self.treeModel, iter, msg, msg, []) else: self.addCommandToModel(command, self.treeModel) view.connect("button-press-event", self.showPopupMenu) self.scriptEngine.monitorSignal("expand preview node", "row-expanded", view) self.scriptEngine.monitorSignal("select preview node", "changed", view) self.scriptEngine.monitorSignal("show preview node options for", "button-press-event", view) scrolled = self.addScrollBar(view) if len(autoGenerated) > 0: frame = gtk.Frame("Current Usecase Preview") frame.add(scrolled) return frame else: return scrolled def convertToUtf8(self, text): return self.convertEncoding(text, 'utf-8', 'replace') def convertToMarkup(self, text): return self.convertEncoding(text, 'ascii', 'xmlcharrefreplace') def convertEncoding(self, text, targetEncoding, replaceMethod): try: return text.encode(targetEncoding, replaceMethod) except ValueError: return text def findPossibleWidgetDescriptions(self, fullWidgetDesc): parts = fullWidgetDesc.split(", ") badNames = [ "Label=OK", "Label=Cancel", "Label=Yes", "Label=No" ] sections, descs, badDescs = [], [], [] for i in range(1, len(parts) + 1): for sectionNameParts in itertools.combinations(parts, i): sectionName = ", ".join(sectionNameParts) actualSection = self.uiMapFileHandler.getSection(sectionName) if actualSection: sections.append(self.convertToUtf8(escape(actualSection))) else: utf8Name = self.convertToUtf8(escape(sectionName)) if sectionName in badNames or (i == 1 and sectionName.startswith("Type=")): badDescs.append(self.getErrorColouredText(utf8Name)) else: descs.append(utf8Name) return sections + descs + badDescs def getWidgetDescriptionWidget(self, possibleWidgetDescs, signalName, widgetType): if len(possibleWidgetDescs) == 1: label = gtk.Label() label.set_markup(possibleWidgetDescs[0]) return label else: liststore = gtk.ListStore(str) for desc in possibleWidgetDescs: liststore.append([ desc ]) combobox = gtk.ComboBox(liststore) cell = gtk.CellRendererText() combobox.pack_start(cell, True) combobox.add_attribute(cell, 'markup', 0) combobox.set_active(0) scriptName = "choose widget description for signal '" + signalName + "' on " + widgetType + " '" + possibleWidgetDescs[0] + "' =" self.scriptEngine.monitorSignal(scriptName, "changed", combobox) return combobox def createTable(self, autoGenerated, dialog): table = gtk.Table(rows=len(autoGenerated) + 1, columns=4) table.set_col_spacings(20) headers = [ "Widget Type", "Identified By", "Action Performed", "Usecase Name" ] for col, header in enumerate(headers): table.attach(self.createMarkupLabel("<b><u>" + header + "</u></b>"), col, col + 1, 0, 1, xoptions=gtk.FILL, yoptions=gtk.FILL) for rowIndex, (command, (widgetType, fullWidgetDesc, signalName)) in enumerate(autoGenerated.items()): table.attach(gtk.Label(widgetType), 0, 1, rowIndex + 1, rowIndex + 2, xoptions=gtk.FILL, yoptions=gtk.FILL) actionDesc = self.getActionDescription(signalName, widgetType) possibleWidgetDescs = self.findPossibleWidgetDescriptions(fullWidgetDesc) widgetDescWidget = self.getWidgetDescriptionWidget(possibleWidgetDescs, signalName, widgetType) table.attach(widgetDescWidget, 1, 2, rowIndex + 1, rowIndex + 2, xoptions=gtk.FILL, yoptions=gtk.FILL) table.attach(gtk.Label(actionDesc), 2, 3, rowIndex + 1, rowIndex + 2, xoptions=gtk.FILL, yoptions=gtk.FILL) entry = gtk.Entry() fieldIdentifier = "for signal '" + signalName + "' on " + widgetType + " '" + removeMarkup(unescape(possibleWidgetDescs[0])) + "'" scriptName = "enter usecase name " + fieldIdentifier + " =" self.scriptEngine.monitorSignal(scriptName, "changed", entry) entry.connect("activate", self.activateEntry, dialog) self.scriptEngine.monitorSignal("press <enter> in field " + fieldIdentifier, "activate", entry) self.allEntries[command] = entry self.allDescriptionWidgets.append(widgetDescWidget) table.attach(entry, 3, 4, rowIndex + 1, rowIndex + 2, yoptions=gtk.FILL) table.show_all() frame = gtk.Frame("Previously unseen actions: provide names for the interesting ones") frame.add(self.addScrollBar(table, viewport=True)) return frame def createPopupMenu(self, widget): menu = gtk.Menu() item = gtk.MenuItem("Create shortcut") deleteItem = gtk.MenuItem("Delete shortcut") updateUIMapItem = gtk.MenuItem("Update UI map file") renameItem = gtk.MenuItem("Rename") separator = gtk.SeparatorMenuItem() menu.append(item) menu.append(deleteItem) menu.append(renameItem) menu.append(separator) menu.append(updateUIMapItem) item.connect("activate", self.createShortcut, widget) deleteItem.connect("activate", self.deleteShortcut, widget) renameItem.connect("activate", self.rename, widget) updateUIMapItem.connect("activate", self.updateUIMap, widget) self.popupSensitivities[item] = self.setCreateShortcutSensitivity self.popupSensitivities[deleteItem] = self.setDeleteShortcutSensitivity self.popupSensitivities[renameItem] = self.setRenameSensitivity self.popupSensitivities[updateUIMapItem] = self.setUpdateUIMapSensitivity self.scriptEngine.monitorSignal("create a new shortcut", "activate", item) self.scriptEngine.monitorSignal("delete shortcut", "activate", deleteItem) self.scriptEngine.monitorSignal("rename a usecase name or shortcut", "activate", renameItem) self.scriptEngine.monitorSignal("update ui map file", "activate", updateUIMapItem) item.show() deleteItem.show() renameItem.show() separator.show() updateUIMapItem.show() return menu def applySensitivities(self, selection): for item, method in self.popupSensitivities.items(): method(item, selection) def setCreateShortcutSensitivity(self, item, selection): # Check selection has at least 2 elements and is consecutive item.set_sensitive(selection.count_selected_rows() > 1 and self.isConsecutive(selection)) def setDeleteShortcutSensitivity(self, item, selection): item.set_sensitive(selection.count_selected_rows() == 1 and self.shortcutsSelected(selection)) def showPopupMenu(self, treeView, event): if event.button == 3: time = event.time pathInfo = treeView.get_path_at_pos(int(event.x), int(event.y)) selection = treeView.get_selection() selectedRows = selection.get_selected_rows() # If they didnt right click on a currently selected # row, change the selection if pathInfo is not None: if pathInfo[0] not in selectedRows[1]: selection.unselect_all() selection.select_path(pathInfo[0]) treeView.grab_focus() self.popup.popup(None, None, None, event.button, time) treeView.emit_stop_by_name("button-press-event") self.applySensitivities(selection) def createShortcut(self, widget, view): selection = view.get_selection() lines, arguments = self.selectionToModel(selection) self.createShortcutFromLines(lines, arguments) def selectionToModel(self, selection): lines = [] allArguments = [] def addSelected(treemodel, path, iter, *args): line = treemodel.get_value(iter, 1) currArgs = treemodel.get_value(iter, 2) lines.append(line) for arg in currArgs: allArguments.append(arg) selection.selected_foreach(addSelected) return lines, allArguments def createShortcutFromLines(self, lines, arguments): dialog = gtk.Dialog("New Shortcut", flags=gtk.DIALOG_MODAL) dialog.set_name("New Shortcut Window") label = gtk.Label("New name for shortcut:") entry = gtk.Entry() entry.set_name("New Name") if arguments: defaultText = "Do something with " + " and ".join(arguments) entry.set_text(defaultText) dialog.vbox.set_spacing(10) dialog.vbox.pack_start(label, expand=False, fill=False) dialog.vbox.pack_start(entry, expand=True, fill=True) dialog.vbox.pack_start(gtk.HSeparator(), expand=False, fill=False) self.scriptEngine.monitorSignal("enter new shortcut name", "changed", entry) shortcutView = self.createShortcutPreview(lines, arguments, entry) frame = gtk.Frame("") frame.get_label_widget().set_use_markup(True) self.updateShortcutName(entry, frame, arguments) frame.add(shortcutView) entry.connect("changed", self.updateShortcutName, frame, arguments) dialog.vbox.pack_end(frame, expand=True, fill=True) yesButton = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) self.scriptEngine.monitorSignal("accept new shortcut name", "clicked", yesButton) cancelButton = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) self.scriptEngine.monitorSignal("cancel new shortcut name", "clicked", cancelButton) dialog.connect("response", self.respond, entry, frame, shortcutView) dialog.show_all() def updateShortcutName(self, textEntry, frame, arguments): newName = textEntry.get_text() for arg in arguments: newName = newName.replace(arg, "$") markup = "<b><i>" + newName.lower().replace(" ", "_") + ".shortcut" + "</i></b>" frame.get_label_widget().set_label(markup) def getShortcutFileName(self, shortcutName): return shortcutName.lower().replace(" ", "_") + ".shortcut" def copyRow(self, iter, parentIter, followIter=None): row = list(self.treeModel.get(iter, 0, 1, 2)) if followIter is None: newIter = self.treeModel.append(parentIter, row) else: newIter = self.treeModel.insert_before(parentIter, followIter, row) subIter = self.treeModel.iter_children(iter) if subIter is not None: self.copyRow(subIter, newIter) def getTopLevelIters(self): iters = [] def addSelected(model, path, iter, *args): if len(path) == 1: iters.append(iter) self.treeModel.foreach(addSelected) return iters def getFirstDifferentIter(self, iters, commands): for i, iter in enumerate(iters): if commands[i] != self.treeModel.get_value(iter, 1): return i, iter def addShortcutToPreview(self): allCommands = self.getAllCommands() while True: topLevelIters = self.getTopLevelIters() topLevelNames = [ self.treeModel.get_value(iter, 1) for iter in topLevelIters ] if topLevelNames == allCommands: break iterIx, iter = self.getFirstDifferentIter(topLevelIters, allCommands) shortcut, args = self.shortcutManager.findShortcut(allCommands[iterIx]) if shortcut: shortcutIter = self.addShortcutCommandToModel(shortcut, args, self.treeModel, None, iter) shortcutLength = len(shortcut.commands) currentIters = topLevelIters[iterIx:iterIx + shortcutLength] for iter in currentIters: self.copyRow(iter, shortcutIter) self.treeModel.remove(iter) else: sys.stderr.write("ERROR: mismatch in files, expected shortcut for '" + allCommands[iterIx] + "', but found none.\n") break def findShortcutIters(self, shortcut): iters = [] def addSelected(model, path, iter, *args): value = model.get_value(iter, 1) args = model.get_value(iter, 2) currShortcut = self.getShortcut(value, args) if currShortcut is shortcut: iters.append(iter) self.treeModel.foreach(addSelected) return iters def removeShortcutFromPreview(self, shortcut): for iter in self.findShortcutIters(shortcut): childIter = self.treeModel.iter_children(iter) parentIter = self.treeModel.iter_parent(iter) nextIter = self.treeModel.iter_next(iter) while childIter is not None: self.copyRow(childIter, parentIter, nextIter) childIter = self.treeModel.iter_next(childIter) self.treeModel.remove(iter) def removeShortcutFromUsecase(self, shortcut): recordScript = RecordScript(self.fileName, []) for iter in self.getTopLevelIters(): value = self.treeModel.get_value(iter, 1) args = self.treeModel.get_value(iter, 2) if self.getShortcut(value, args) is shortcut: childIter = self.treeModel.iter_children(iter) while childIter is not None: recordScript.record(self.treeModel.get_value(childIter, 1)) childIter = self.treeModel.iter_next(childIter) else: recordScript.record(value) def respond(self, dialog, responseId, entry, frame, shortcutView): if responseId == gtk.RESPONSE_ACCEPT: if self.checkShortcutName(dialog, entry.get_text().lower()): dialog.hide() shortcut = self.saveShortcut(frame.get_label(), self.getShortcutLines(shortcutView)) self.shortcutManager.add(shortcut) if self.isAutoGenerated: self.createdShortcuts.append(shortcut.name) self.recreateUsecaseFile() self.addShortcutToPreview() else: dialog.hide() def recreateUsecaseFile(self): recordScript = RecordScript(self.fileName, [shortcut for _, shortcut in self.shortcutManager.shortcuts]) for iter in self.getTopLevelIters(): value =self.treeModel.get_value(iter, 1) if "<b>" in self.treeModel.get_value(iter, 0): args = self.treeModel.get_value(iter, 2) shortcut = self.getShortcut(value, args) self.runShortcutCommands(recordScript, shortcut, args) else: recordScript.record(value) def runShortcutCommands(self, recordScript, shortcut, args): shortcutCopy = ReplayScript(shortcut.name, True) while not shortcutCopy.hasTerminated(): command = shortcutCopy.getCommand(args) self.recordShortcutCommand(recordScript, command) def recordShortcutCommand(self, recordScript, command): shortcut, args = self.shortcutManager.findShortcut(command) if shortcut: self.runShortcutCommands(recordScript, shortcut, args) else: recordScript.record(command) def getShortcut(self, shortcutNameWithArgs, args): for _, shortcut in self.shortcutManager.shortcuts: if shortcut.getShortcutNameWithArgs(args) == shortcutNameWithArgs: return shortcut def getShortcutLines(self, shortcutView): model = shortcutView.get_model() lines = [] def addSelected(model, path, iter, *args): lines.append(model.get_value(iter, 0)) model.foreach(addSelected) return lines def checkShortcutName(self, parent, name): if not name: self.showErrorDialog(parent, "The shortcut name can't be empty.") return False elif self.isInUIMap(name): self.showErrorDialog(parent, "The shortcut name already exists in the UI map file.") return False elif self.isInShortcuts(name): self.showErrorDialog(parent, "The shortcut name is already being used for another shortcut.") return False return True def isInUIMap(self, name): return len(self.uiMapFileHandler.findSectionsAndOptions(name)) > 0 def isInShortcuts(self, name): return self.shortcutManager.findShortcut(name)[0] is not None def saveShortcut(self, name, lines): storytextDir = os.environ["STORYTEXT_HOME"] if not os.path.isdir(storytextDir): os.makedirs(storytextDir) fileName = os.path.join(storytextDir, name) with open(fileName, "w") as f: for line in lines: f.write(line + "\n") print "Shortcut", repr(name), "created." return ReplayScript(fileName) def shortcutsSelected(self, selection): shortcuts = [] def addSelected(treemodel, path, iter, *args): shortcuts.append("<b>" in treemodel.get_value(iter, 0)) selection.selected_foreach(addSelected) return any(shortcuts) def isConsecutive(self, selection): paths = [] def addSelected(treemodel, path, *args): paths.append(path) selection.selected_foreach(addSelected) prevIx = None for path in paths: if len(path) > 1: return False # Can't make shortcuts out of lines further down the hierarchy ix = path[0] if prevIx is not None and ix - prevIx > 1: return False prevIx = ix return True def showConfirmationDialog(self, parent, message, *args): self.showErrorWarningDialog(parent, message, gtk.MESSAGE_WARNING, "Confirmation", gtk.BUTTONS_OK_CANCEL, *args) def showErrorDialog(self, parent, message): self.showErrorWarningDialog(parent, message, gtk.MESSAGE_ERROR, "Error", gtk.BUTTONS_OK) def showErrorWarningDialog(self, parent, message, stockIcon, alarmLevel, buttons, *args): dialog = self.createMessageDialog(parent, message, stockIcon, alarmLevel, buttons) dialog.connect("response", self.respondErrorWarning, *args) dialog.show_all() def respondErrorWarning(self, dialog, response, *args): dialog.hide() if response == gtk.RESPONSE_OK and args: args[0](*args[1:]) def createMessageDialog(self, parent, message, stockIcon, alarmLevel, buttons=gtk.BUTTONS_OK): dialogTitle = "StoryText " + alarmLevel dialog = gtk.MessageDialog(parent, gtk.DIALOG_MODAL, stockIcon, buttons, None) # Would like to use dialog.get_widget_for_response(gtk.RESPONSE_OK), introduced in gtk 2.22 instead for button in dialog.action_area.get_children(): response = dialog.get_response_for_widget(button) if response == gtk.RESPONSE_OK: self.scriptEngine.monitorSignal("accept message", "clicked", button) elif response == gtk.RESPONSE_CANCEL: self.scriptEngine.monitorSignal("cancel message", "clicked", button) dialog.set_title(dialogTitle) dialog.set_markup(message) dialog.set_default_response(gtk.RESPONSE_OK) return dialog def createShortcutPreview(self, commands, arguments, textEntry): listModel = gtk.ListStore(str, str, object) view = gtk.TreeView(listModel) view.set_headers_visible(False) cmdRenderer = gtk.CellRendererText() cmdColumn = gtk.TreeViewColumn("", cmdRenderer, text=0) view.append_column(cmdColumn) view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) argumentIndex = 0 for command in commands: shortcut, args =self.shortcutManager.findShortcut(command) if shortcut: text = shortcut.getShortcutName() else: arg = arguments[argumentIndex] if argumentIndex < len(arguments) else "" text, argument = self.replaceArguments(command, arg) args = [argument] if argument else [] if argument: argumentIndex = argumentIndex + 1 % len(arguments) iter1 = listModel.append([text, command, args]) if not shortcut and args: textEntry.connect("changed", self.handleArguments, (listModel, iter1)) self.scriptEngine.monitorSignal("select preview node", "changed", view) self.scriptEngine.monitorSignal("show preview node options for", "button-press-event", view) return view def replaceArguments(self, command, argument): if argument and command.endswith(argument): return command.replace(argument, "$"), argument else: return command, "" def handleArguments(self, widget, data): model, iter = data newName = widget.get_text() originalValue = model.get_value(iter, 1) currentValue = model.get_value(iter, 0) args = model.get_value(iter, 2) if len(args) == 1 and re.search("\\b" + args[0] + "\\b", newName): if originalValue == currentValue: model.set_value(iter, 0, currentValue.replace(args[0], "$")) else: model.set_value(iter, 0, originalValue) def setUpdateUIMapSensitivity(self, item, selection): if selection.count_selected_rows() == 1 and self.uiMapSelected(selection): item.set_sensitive(True) else: item.set_sensitive(False) def uiMapSelected(self, selection): uiMaps = [] def addSelected(treemodel, path, iter, *args): uiMaps.append(" on widget identified by " in treemodel.get_value(iter, 0)) selection.selected_foreach(addSelected) return any(uiMaps) def updateUIMap(self, widget, view): selection = view.get_selection() signals, widgetDescriptions = self.selectionToModel(selection) self.createUIMapDialog(signals[0], widgetDescriptions[0]) def createUIMapDialog(self, signal, widgetDescription): cmd = self.uiMapFileHandler.get(widgetDescription, signal) dialog = gtk.Dialog('Update UI map for ' + "'" + cmd + "'", flags=gtk.DIALOG_MODAL) dialog.vbox.set_spacing(10) label = gtk.Label("Widget description") widgetDescEntry = gtk.Entry() widgetDescEntry.set_text(widgetDescription) hbox = gtk.HBox(False, 10) hbox.pack_start(label, expand=False, fill=False) hbox.pack_start(widgetDescEntry, expand=True, fill=True) dialog.vbox.pack_start(hbox, expand=False, fill=False) label2 = gtk.Label("Activity") signalEntry = gtk.Entry() signalEntry.set_text(signal) hbox2 = gtk.HBox(False, 10) hbox2.pack_start(label2, expand=False, fill=False) hbox2.pack_start(signalEntry, expand=True, fill=True) dialog.vbox.pack_start(hbox2, expand=False, fill=False) updateButton = dialog.add_button('Update', gtk.RESPONSE_ACCEPT) cancelButton = dialog.add_button('Cancel', gtk.RESPONSE_CANCEL) self.scriptEngine.monitorSignal("enter new widget description", "changed", widgetDescEntry) self.scriptEngine.monitorSignal("enter new activity name", "changed", signalEntry) self.scriptEngine.monitorSignal("accept update ui map file", "clicked", updateButton) self.scriptEngine.monitorSignal("cancel update ui map file", "clicked", cancelButton) dialog.connect("response", self.respondUpdateUIMap, signalEntry, widgetDescEntry, signal, widgetDescription) dialog.show_all() def respondUpdateUIMap(self, dialog, responseId, signalEntry, widgetDescEntry, oldSignal, oldWidgetDescription): if responseId == gtk.RESPONSE_ACCEPT: newSignal = signalEntry.get_text() newWidgetDesc = widgetDescEntry.get_text() if self.checkUpdateUIMapEntryNames(dialog, newWidgetDesc, newSignal): dialog.hide() self.uiMapFileHandler.updateSectionAndOptionNames(oldWidgetDescription, newWidgetDesc, oldSignal, newSignal) self.updateUIMapPreview(oldWidgetDescription, newWidgetDesc, oldSignal, newSignal) else: dialog.hide() def updateUIMapPreview(self, oldWidgetDescription, newWidgetDesc, oldSignal, newSignal): leafIters = self.getLeafIters() for iter in leafIters: descValue = self.treeModel.get_value(iter, 2) if descValue: if oldWidgetDescription == descValue[0]: signalValue = self.treeModel.get_value(iter, 1) if signalValue != oldSignal: newSignal = oldSignal msg = self.makeUIMapMessage(newSignal, newWidgetDesc) self.treeModel.set(iter, 0, msg, 1, newSignal, 2, [newWidgetDesc]) def getLeafIters(self): iters = [] def addLeafIters(model, path, iter, *args): if not model.iter_has_child(iter): iters.append(iter) self.treeModel.foreach(addLeafIters) return iters def checkUpdateUIMapEntryNames(self, parent, widgetName, activityName): if not widgetName: self.showErrorDialog(parent, "The widget name can't be empty.") return False elif not activityName: self.showErrorDialog(parent, "The activity name can't be empty.") return False return True def setRenameSensitivity(self, item, selection): if selection.count_selected_rows() == 1 and (self.shortcutsSelected(selection) or (self.usecaseSelected(selection) and self.isNewName(selection))): item.set_sensitive(True) else: item.set_sensitive(False) def isNewName(self, selection): newNames = [] def addNames(treeModel, path, iter, *args): command = treeModel.get_value(iter, 1) cmd, args = self.uiMapFileHandler.splitOptionValue(command) if cmd: newNames.append(cmd) selection.selected_foreach(addNames) return any(newNames) def usecaseSelected(self, selection): return not self.shortcutsSelected(selection) and not self.uiMapSelected(selection) \ and not self.waitCommandSelected(selection) def waitCommandSelected(self, selection): commands, _ = self.selectionToModel(selection) return any(cmd.startswith(waitCommandName) for cmd in commands) def rename(self, widget, view): selection = view.get_selection() commands, _ = self.selectionToModel(selection) self.createRenameDialog(commands[0], self.shortcutsSelected(selection)) def deleteShortcut(self, menuItem, view): selection = view.get_selection() command = self.selectionToModel(selection)[0][0] shortcut, args = self.shortcutManager.findShortcut(command) confirmationMessage = "You are about to delete the file '" + os.path.basename(shortcut.name) + "'\nand remove all references to it in the current usecase." self.showConfirmationDialog(menuItem.get_toplevel(), confirmationMessage, self.performShortcutDeletion, shortcut) def performShortcutDeletion(self, shortcut): print "ShortcutRemove", repr(shortcut.getShortcutRegexp().pattern), "renamed to '" + "\\n".join(shortcut.commands) + "'" self.removeShortcutFromUsecase(shortcut) self.removeShortcutFromPreview(shortcut) self.shortcutManager.remove(shortcut) def createRenameDialog(self, command, isShortcut=False): if isShortcut: name = 'shortcut' shortcut, args = self.shortcutManager.findShortcut(command) cmd = shortcut.getShortcutName() else: cmd, args = self.uiMapFileHandler.splitOptionValue(command) name = "usecase" dialog = gtk.Dialog('Rename ' + name, flags=gtk.DIALOG_MODAL) dialog.vbox.set_spacing(10) label = gtk.Label(name.title()) nameEntry = gtk.Entry() nameEntry.set_text(cmd) hbox = gtk.HBox(False, 10) hbox.pack_start(label, expand=False, fill=False) hbox.pack_start(nameEntry, expand=True, fill=True) dialog.vbox.pack_start(hbox, expand=False, fill=False) renameButton = dialog.add_button('Rename', gtk.RESPONSE_ACCEPT) cancelButton = dialog.add_button('Cancel', gtk.RESPONSE_CANCEL) self.scriptEngine.monitorSignal("rename " + name, "changed", nameEntry) self.scriptEngine.monitorSignal("accept rename", "clicked", renameButton) self.scriptEngine.monitorSignal("cancel rename", "clicked", cancelButton) dialog.connect("response", self.respondRenameShortcut if isShortcut else self.respondRenameUsecase, nameEntry, cmd) dialog.show_all() def respondRenameUsecase(self, dialog, responseId, nameEntry, oldValue): methodName = "UsecaseRename" newValue = nameEntry.get_text() if responseId == gtk.RESPONSE_ACCEPT: if self.checkUsecaseName(dialog, newValue): self.updateUsecaseNameInUIMap(oldValue, newValue) self.updateUsecaseNameInShorcuts(oldValue, newValue) self.replaceInFile(self.fileName, self.makeReplacement, [(oldValue, newValue)]) print encodingutils.encodeToLocale(methodName + " '" + oldValue + "' renamed to '" + newValue + "'") self.initShortcutManager() self.updateNameInPreview(oldValue, newValue) dialog.hide() else: dialog.hide() def respondRenameShortcut(self, dialog, responseId, nameEntry, oldValue): methodName = "ShortcutRename" newValue = nameEntry.get_text() if responseId == gtk.RESPONSE_ACCEPT: if self.checkShortcutName(dialog, newValue) and self.checkShortcutArguments(dialog, oldValue, newValue): self.shortcutManager.rename(oldValue, self.getShortcutFileName(newValue)) oldValueRegexp = ReplayScript.transformToRegexp(oldValue) # Update shortcut name in shortcut files for _, shortcut in self.shortcutManager.getShortcuts(): if shortcut.getShortcutName() != newValue: self.replaceInFile(shortcut.name, self.replaceShortcutName, oldValueRegexp, newValue) # Update shortcut name in current usecase file self.replaceInFile(self.fileName, self.replaceShortcutName, oldValueRegexp, newValue) print methodName, repr(oldValueRegexp), "renamed to", repr(newValue) self.initShortcutManager() self.updateShortcutNameInPreview(oldValueRegexp, newValue) dialog.hide() else: nameEntry.set_text(oldValue) else: dialog.hide() def checkUsecaseName(self, parent, name): if not name: self.showErrorDialog(parent, "The usecase name can't be empty.") return False elif self.isInUIMap(name): self.showErrorDialog(parent, "The usecase name already exists in the UI map file.") return False return True def updateUsecaseNameInUIMap(self, oldCommand, newCommand): for section, option in self.uiMapFileHandler.findSectionsAndOptions(oldCommand): self.uiMapFileHandler.updateOptionValue(section, option, newCommand) def updateUsecaseNameInShorcuts(self, oldCommand, newCommand): for _, shortcut in self.shortcutManager.getShortcuts(): self.replaceInFile(shortcut.name, self.makeReplacement, [(oldCommand, newCommand)]) def checkShortcutArguments(self, parentDialog, oldShortcut, newShortcut): numArgs = oldShortcut.count("$") if numArgs != newShortcut.count("$"): self.showErrorDialog(parentDialog, "The number of shortcut arguments('$') must be "+ str(numArgs)) return False return True def updateNameInPreview(self, oldValue, newValue): def updateNode(model, path, iter, *args): markup = model.get_value(iter, 0) text = model.get_value(iter, 1) if text.startswith(oldValue): model.set_value(iter, 0, markup.replace(oldValue, newValue)) model.set_value(iter, 1, text.replace(oldValue, newValue)) self.treeModel.foreach(updateNode) def updateShortcutNameInPreview(self, oldNameRegexp, newName): def updateNode(model, path, iter, *args): markup = model.get_value(iter, 0) if markup.startswith("<b>"): text = model.get_value(iter, 1) newText = self.replaceShortcutName(text, 0, oldNameRegexp , newName) newMarkup = self.replaceShortcutName(markup, 0, oldNameRegexp, newName) if text != newText: if newMarkup == markup: newMarkup = newMarkup.replace(text, newText) model.set_value(iter, 0, newMarkup) model.set_value(iter, 1, newText) self.treeModel.foreach(updateNode) def regexpReplace(self, regexp, line, newText): return re.sub(regexp, newText, line) def replaceShortcutName(self, line, position, oldNameRegexp, newName): def replaceArgs(matchobj): return ReplayScript.getTextWithArgs(newName, [arg for arg in matchobj.groups()]) return self.regexpReplace(oldNameRegexp, line, replaceArgs)