class TestKeyboard(TestCase): def setUp(self): """ Record all open windows :return: """ self.ahk = AHK() self.before_windows = self.ahk.windows() self.p = subprocess.Popen("notepad") time.sleep(1) self.notepad = self.ahk.find_window(title=b"Untitled - Notepad") def tearDown(self): self.p.terminate() time.sleep(0.2) def test_window_send(self): self.notepad.send("hello world") time.sleep(1) self.assertIn(b"hello world", self.notepad.text) def test_send(self): self.notepad.activate() self.ahk.send("hello world") assert b"hello world" in self.notepad.text def test_send_key_mult(self): self.notepad.send(KEYS.TAB * 4) time.sleep(0.5) self.assertEqual(self.notepad.text.count(b"\t"), 4, self.notepad.text) def test_send_input(self): self.notepad.activate() self.ahk.send_input("Hello World") assert b"Hello World" in self.notepad.text def test_type(self): self.notepad.activate() self.ahk.type("Hello, World!") assert b"Hello, World!" in self.notepad.text def test_type_escapes_equals(self): """ https://github.com/spyoungtech/ahk/issues/96 """ self.notepad.activate() self.ahk.type("=foo") assert b"=foo" in self.notepad.text def test_sendraw_equals(self): """ https://github.com/spyoungtech/ahk/issues/96 """ self.notepad.activate() self.ahk.send_raw("=foo") assert b"=foo" in self.notepad.text def test_set_capslock_state(self): self.ahk.set_capslock_state("on") assert self.ahk.key_state("CapsLock", "T")
class TestKeyboard(TestCase): def setUp(self): """ Record all open windows :return: """ self.ahk = AHK() self.before_windows = self.ahk.windows() self.p = subprocess.Popen('notepad') time.sleep(1) self.notepad = self.ahk.find_window(title=b'Untitled - Notepad') def tearDown(self): self.p.terminate() time.sleep(0.2) def test_window_send(self): self.notepad.send('hello world') time.sleep(1) self.assertIn(b'hello world', self.notepad.text) def test_send(self): self.notepad.activate() self.ahk.send('hello world') assert b'hello world' in self.notepad.text def test_send_key_mult(self): self.notepad.send(KEYS.TAB * 4) time.sleep(0.5) self.assertEqual(self.notepad.text.count(b'\t'), 4, self.notepad.text) def test_send_input(self): self.notepad.activate() self.ahk.send_input('Hello World') assert b'Hello World' in self.notepad.text def test_type(self): self.notepad.activate() self.ahk.type('Hello, World!') assert b'Hello, World!' in self.notepad.text def test_type_escapes_equals(self): ''' https://github.com/spyoungtech/ahk/issues/96 ''' self.notepad.activate() self.ahk.type('=foo') assert b'=foo' in self.notepad.text def test_sendraw_equals(self): ''' https://github.com/spyoungtech/ahk/issues/96 ''' self.notepad.activate() self.ahk.send_raw('=foo') assert b'=foo' in self.notepad.text
class TestKeyboard(TestCase): def setUp(self): """ Record all open windows :return: """ self.ahk = AHK() self.before_windows = self.ahk.windows() self.p = subprocess.Popen('notepad') time.sleep(1) self.notepad = self.ahk.find_window(title=b'Untitled - Notepad') def tearDown(self): self.p.terminate() time.sleep(0.2) def test_window_send(self): self.notepad.send('hello world') time.sleep(1) self.assertIn(b'hello world', self.notepad.text) @pytest.mark.flaky(reruns=5) def test_window_send_raw(self): self.notepad.send('{Tab 4}', raw=True, delay=10, press_duration=10) time.sleep(0.5) assert b'{Tab 4}' in self.notepad.text def test_send(self): self.notepad.activate() self.ahk.send('hello world') assert b'hello world' in self.notepad.text def test_send_key_mult(self): self.notepad.send(KEYS.TAB * 4) time.sleep(0.5) self.assertEqual(self.notepad.text.count(b'\t'), 4, self.notepad.text) def test_send_input(self): self.notepad.activate() self.ahk.send_input('Hello World') time.sleep(0.5) assert b'Hello World' in self.notepad.text def test_type(self): self.notepad.activate() self.ahk.type('Hello, World!') assert b'Hello, World!' in self.notepad.text def test_type_escapes_equals(self): """ https://github.com/spyoungtech/ahk/issues/96 """ self.notepad.activate() self.ahk.type('=foo') assert b'=foo' in self.notepad.text def test_sendraw_equals(self): """ https://github.com/spyoungtech/ahk/issues/96 """ self.notepad.activate() self.ahk.send_raw('=foo') assert b'=foo' in self.notepad.text def test_set_capslock_state(self): self.ahk.set_capslock_state('on') assert self.ahk.key_state('CapsLock', 'T')
class SolutionBrowser(QMainWindow): def __init__(self, setToLoad=None): super(SolutionBrowser, self).__init__() # parse config self.parse_config() # overwrite default set if provided if setToLoad: self.default_set = setToLoad # set size mainwindow self.setWindowTitle('Solution Browser') self.resize(self.hsize, self.vsize) if self.isStartMaximized: self.setWindowState(Qt.WindowMaximized) font = QFont() font.setPointSize(10) self.setFont(font) # icon app_icon = QIcon() app_icon.addFile(os.path.join('icons', 'icon256.png'), QSize(256, 256)) app_icon.addFile(os.path.join('icons', 'icon128.png'), QSize(128, 128)) self.setWindowIcon(app_icon) # statusbar self.statusbar = self.statusBar() self.statusbar_style_alert = "QStatusBar{font-size:10pt;background:rgba(250, 128, 114, 1);color:black;font-weight:bold;}" self.statusbar_style_normal = "QStatusBar{font-size:10pt;color:black;font-weight:bold;}" self.statusbar.setStyleSheet(self.statusbar_style_normal) self.statusbar.showMessage('Starting SolutionBrowser') # load layout top and bottom frames self.layouts = SolutionBrowserLayout(self) self.setCentralWidget(self.layouts) self.createActions() self.createMenus() # create par dialog self.parDialogOpen = False self.parDialog = ParDialog(self) # get frames for easy reference. self.ImageViewerFrame = self.layouts.ImageViewerFrame self.ParameterFrame = self.layouts.ParameterFrame self.ParameterFrame.setFont(font) # start image viewer self.setup_image_viewer() # load default image self.open_image('default.jpg') # start parameter selection tool self.setup_parameter_selector() # ahk to communicate with matlab if AHK: self.ahk = AHK(executable_path=self.ahk_executable_path) def resizeEvent(self, event): self.fitToWindow(True) def setup_image_viewer(self): self.scaleFactor = 0.0 self.reuseScaleFactor = None self.imageLabel = QLabel(self.ImageViewerFrame) self.imageLabel.setBackgroundRole(QPalette.Base) self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setScaledContents(True) self.scrollArea = QScrollArea(self.ImageViewerFrame) self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.imageLabel) layout = QHBoxLayout() layout.addWidget(self.scrollArea) # do not add image label layout.setContentsMargins(1, 1, 1, 1) self.ImageViewerFrame.setLayout(layout) def setup_parameter_selector(self): if self.default_set: self.open_batch(batchFolder=self.default_set) else: self.open_batch() # init h layout layout = QHBoxLayout() # create the groups self.parFrames = [] self.parLabels = [] self.parBoxes = [] self.parSliders = [] self.valIndices = [] # create parameter selection boxes (i.e. slider groups) for idx, name, values in zip(range(len(self.parNames)), self.parNames, self.uniqueVals): frame, label, valueBox, slider, valIdx = self.createSliderGroup(idx, name, values) self.parFrames.append(frame) self.parLabels.append(label) self.parBoxes.append(valueBox) self.parSliders.append(slider) self.valIndices.append(valIdx) layout.addWidget(frame) # create overview section. ov_frame, simnum_label = self.createOverviewGroup() self.simnumLabel = simnum_label layout.addWidget(ov_frame) layout.setContentsMargins(1, 1, 1, 1) self.ParameterFrame.setLayout(layout) # call to fix sim number self.updateImage() self.updateOverviewGroup() def createOverviewGroup(self): # init frame and layout frame = QFrame(self.ParameterFrame) grid_layout = QGridLayout() frame.setLayout(grid_layout) # set simnum if not yet availble if not hasattr(self, 'simNum'): self.simNum = 000 if not hasattr(self, 'simImgPath'): self.simImgPath = 'null' # create sim num label simnum_label = QLabel(frame) simnum_label.setText('Sim num: %03i' % self.simNum) simnum_label.setStyleSheet("QLabel{color:darkblue;font-weight:bold;}") # create prev and next button prev_but = CtrlButton(frame) next_but = CtrlButton(frame) prev_but.setIcon(self.style().standardIcon(getattr(QStyle, "SP_MediaSeekBackward"))) next_but.setIcon(self.style().standardIcon(getattr(QStyle, "SP_MediaSeekForward"))) prev_but.clicked.connect(self.callUpdateImageDown) next_but.clicked.connect(self.callUpdateImageUp) # load to matlab load_but = QPushButton(frame) load_but.setText('Load in Matlab') load_but.clicked.connect(self.loadInMatlab) # view gif view_gif_but = QPushButton(frame) view_gif_but.setText('View gif') view_gif_but.clicked.connect(self.viewGif) # view parameters par_but = QPushButton('Parameters', frame) par_but.clicked.connect(self.viewParameters) # add things layout grid_layout.addWidget(simnum_label, 0, 0, 1, 1) grid_layout.addWidget(par_but, 0, 1, 1, 1) grid_layout.addWidget(prev_but, 1, 0, 1, 1) grid_layout.addWidget(next_but, 1, 1, 1, 1) grid_layout.addWidget(load_but, 2, 0, 1, 1) grid_layout.addWidget(view_gif_but, 2, 1, 1, 1) return frame, simnum_label def callUpdateImageUp(self): sender = self.sender() inc = 1 if isinstance(sender, CtrlButton): if sender.isCtrlPressed(): inc = 10 if self.simNum <= self.totalNumSims - inc: self.simNum += inc self.updateImage(self.simNum) self.updateSliders() else: self.statusbar.showMessage('Last simulation reached') self.statusbar.setStyleSheet(self.statusbar_style_alert) def callUpdateImageDown(self): sender = self.sender() inc = 1 if isinstance(sender, CtrlButton): if sender.isCtrlPressed(): inc = 10 if self.simNum >= inc + 1: self.simNum -= inc self.updateImage(self.simNum) self.updateSliders() else: self.statusbar.showMessage('First simulation reached') self.statusbar.setStyleSheet(self.statusbar_style_alert) def updateOverviewGroup(self): # update simnum label self.simnumLabel.setText('Sim num: %03i' % self.simNum) # update statusbar self.statusbar.setStyleSheet(self.statusbar_style_normal) self.statusbar.showMessage('Simulation %03i loaded: %s' % (self.simNum, self.simImgPath)) # if parDialog open, update it if self.parDialogOpen: text = self.getParameterText() self.parDialog.updateText(text) def loadInMatlab(self): # get the name of the current file row_idx = self.simNum - 1 matFileName = self.parData['matFile'].iloc[row_idx] # open matlab. Expects ahk script to be running on system. # script maps ctrl + m to open matlab command window if hasattr(self, 'ahk'): self.ahk.send('^m') sleep(0.100) # short delay self.ahk.type('clear;load(\'' + matFileName + '\');') self.ahk.send('{Enter}') else: self.statusbar.showMessage( 'AHK not found! Could not import AHK, make sure it is installed.') def viewParameters(self): if not self.parDialogOpen: # create new window self.parDialogOpen = True self.parDialog.show() # put in the text text = self.getParameterText() self.parDialog.updateText(text) else: self.parDialog.close() def viewGif(self): # get the name of the current file row_idx = self.simNum - 1 gifFileName = self.parData['gifFile'].iloc[row_idx] # check if file exist: if os.path.isfile(gifFileName): os.startfile(gifFileName, 'open') else: self.statusbar.showMessage('GIF does not exist for %03i...' % self.simNum) self.statusbar.setStyleSheet(self.statusbar_style_alert) def createSliderGroup(self, idx, parameterName, parameterValues): # init frame and layout frame = QFrame(self.ParameterFrame) grid_layout = QGridLayout() frame.setLayout(grid_layout) # callback functions TEST def sliderChange(): self.valChange(idx, parameterName, 'slider') def boxChange(): self.valChange(idx, parameterName, 'box') # fill in grid label = QLabel(frame) label.setText(parameterName) valIdx = floor((len(parameterValues) - 1) / 2) valueBox = QComboBox(frame) valueBox.addItems([str(x) for x in parameterValues]) valueBox.setCurrentIndex(valIdx) valueBox.currentIndexChanged.connect(boxChange) slider = JumpSlider(Qt.Horizontal) slider.setRange(0, len(parameterValues) - 1) slider.setValue(valIdx) slider.setTickPosition(QSlider.TicksBothSides) slider.setTickInterval(1) slider.setSingleStep(1) slider.valueChanged.connect(sliderChange) grid_layout.addWidget(label, 0, 0, 1, 1) grid_layout.addWidget(valueBox, 0, 1, 1, 1) grid_layout.addWidget(slider, 1, 0, 2, 2) return frame, label, valueBox, slider, valIdx def valChange(self, parIdx, parName, source): if source == 'slider': valueIdx = self.parSliders[parIdx].value() self.parBoxes[parIdx].setCurrentIndex(valueIdx) elif source == 'box': valueIdx = self.parBoxes[parIdx].currentIndex() self.parSliders[parIdx].setValue(valueIdx) self.valIndices[parIdx] = valueIdx # only update image once (box is called when slider is changed and vice versa.) if source == 'slider': self.updateImage() def updateSliders(self): # get par values based on row idx row_idx = self.simNum - 1 data_row = self.parData.iloc[row_idx] # need to determine parameter unique value valIndex based on sim number # get values based on index for idx, name in enumerate(self.parNames): value = self.parData[name].iloc[row_idx] result = np.where(self.uniqueVals[idx] == value) self.valIndices[idx] = int(result[0]) # update sliders for parIdx, valIdx in enumerate(self.valIndices): self.parSliders[parIdx].setValue(valIdx) def updateImage(self, simNum=None): # if sim num provided skip first section if not simNum: # get values based on index parValues = [None] * len(self.valIndices) for idx, val in enumerate(self.valIndices): parValues[idx] = self.uniqueVals[idx][val] # select the right row: criteria_list = [] for idx, name in enumerate(self.parNames): df = self.parData[name] == parValues[idx] criteria_list.append(df.values) critArray = np.array(criteria_list).transpose() row = critArray.all(axis=1) row_idx = self.parData[pd.Series(row)].index[0] simNum = row_idx + 1 else: row_idx = simNum - 1 imgFileName = self.parData['imgFile'].iloc[row_idx] # set the simNum self.simNum = simNum self.simImgPath = imgFileName # update the label with the new simNim self.updateOverviewGroup() # open the image self.open_image(imgFileName) def open_batch(self, batchFolder=None): # open folder browser baseFolder = self.base_folder if not batchFolder: batchFolder = QFileDialog.getExistingDirectory(self, "Open Directory", baseFolder) else: batchFolder = os.path.join(baseFolder, batchFolder) if batchFolder: # get simulation name fc = os.listdir(batchFolder) fc = [f for f in fc if 'parlist' not in f and 'params' not in f] fc = [f.split('_')[0] for f in fc if '_' in f] simulationName = list(set(fc)) if len(simulationName) > 1: raise('ERROR, could not determine simulation name') else: simulationName = simulationName[0] # read csv as dataframe: self.parData = pd.read_csv(os.path.join(batchFolder, self.parlist_filename)) self.totalNumSims = self.parData.shape[0] # get parameter names: parNames = list(self.parData.columns) parNames.remove('SimNum') self.parNames = parNames # get unique values per paramter uniqueVals = [] for par in parNames: uniqueVals.append(self.parData[par].unique()) self.uniqueVals = uniqueVals # add file locations to data frame fImgNameBase = '{0}_{1}\\fig\\overview_{0}_{1}.png'.format(simulationName, '%03i') fMatNameBase = '{0}_{1}\\{0}_{1}_workspace.mat'.format(simulationName, '%03i') fGifNameBase = '{0}_{1}\\fig\\fiber_radius_{0}_{1}.gif'.format( simulationName, '%03i') # fiber_radius_M500_078 imgFiles = [] matFiles = [] gifFiles = [] for num in list(self.parData['SimNum']): imgFiles.append(os.path.join(batchFolder, fImgNameBase % (num, num))) matFiles.append(os.path.join(batchFolder, fMatNameBase % (num, num))) gifFiles.append(os.path.join(batchFolder, fGifNameBase % (num, num))) self.parData['imgFile'] = imgFiles self.parData['matFile'] = matFiles self.parData['gifFile'] = gifFiles def getParameterText(self): # load matfile row_idx = self.simNum - 1 matFilePath = self.parData['matFile'].iloc[row_idx] try: mat = MatFileLoader.loadmat(matFilePath, variable_names=['P']) except FileNotFoundError: mat = None if mat: # get parameters and format as strings P = mat['P'] # dict for struct P # TODO: delete some less meaningful parameters: keys_to_delete = [''] # make text list text_list = [] str_lengths = np.zeros((len(P), 2)) for idx, (key, value) in enumerate(P.items()): if isinstance(value, np.ndarray): if value.size == 0: value = None if value: if value < 0: value = '%.3e' % value elif value >= 0: value = ' %.3e' % value # space for - sign alignment else: value = '' text_list.append([key, value]) str_lengths[idx, 0] = len(key) str_lengths[idx, 1] = len(value) # find pad length pad_col1 = int(str_lengths[:, 0].max()) + 2 pad_col2 = int(str_lengths[:, 1].max()) + 2 text = '' for t in text_list: name = t[0].rjust(pad_col1) value = t[1].ljust(pad_col2) text += name + '\t:\t' + value + '\n' return text else: return 'mat file not found... :(' def open_image(self, fileName=None): if not fileName: fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) else: image = QImage(fileName) if image.isNull(): self.statusbar.showMessage('Failed to load %03i: %s' % (self.simNum, self.simImgPath)) self.statusbar.setStyleSheet(self.statusbar_style_alert) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() if self.reuseScaleFactor: self.scaleFactor = self.reuseScaleFactor self.scaleImage(self.scaleFactor, isAbsolute=True) else: self.scaleFactor = 1.0 def zoomIn(self): self.scaleImage(1.25) def zoomOut(self): self.scaleImage(0.8) def normalSize(self): self.imageLabel.adjustSize() self.scaleFactor = 1.0 def fitToWindow(self, isResizeEvent=False): fitToWindow = self.fitToWindowAct.isChecked() if fitToWindow: # reset image size self.normalSize() # keep aspect ratio... image_size = self.imageLabel.pixmap().size() area_size = self.scrollArea.viewport().size() f_width = area_size.width() / image_size.width() f_height = area_size.height() / image_size.height() new_factor = min((f_width, f_height)) self.scaleImage(new_factor) self.reuseScaleFactor = new_factor # if not isResizeEvent: # self.normalSize() self.updateActions() def createActions(self): self.openAct = QAction("&Open...", self, shortcut="Ctrl+O", triggered=self.open_image) self.openBatchAct = QAction("&Open Batch...", self, shortcut="Ctrl+B", triggered=self.open_batch) self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close) self.zoomInAct = QAction("Zoom &In (25%)", self, shortcut="Ctrl+=", enabled=False, triggered=self.zoomIn) self.zoomOutAct = QAction("Zoom &Out (25%)", self, shortcut="Ctrl+-", enabled=False, triggered=self.zoomOut) self.normalSizeAct = QAction("&Normal Size", self, shortcut="Ctrl+S", enabled=False, triggered=self.normalSize) self.fitToWindowAct = QAction("&Fit to Window", self, enabled=False, checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow) self.openParAct = QAction("&View Parameters", self, shortcut="Ctrl+p", triggered=self.viewParameters) self.closeWindow = QShortcut(QKeySequence("Ctrl+W"), self) self.closeWindow.activated.connect(self.close) self.nextShortcut = QShortcut(QKeySequence("Right"), self) self.prevShortcut = QShortcut(QKeySequence("Left"), self) self.nextShortcut.activated.connect(self.callUpdateImageUp) self.prevShortcut.activated.connect(self.callUpdateImageDown) def createMenus(self): self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.openBatchAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.openParAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.helpMenu = QMenu("&Help", self) # self.helpMenu.addAction(self.aboutAct) self.menuBar().addMenu(self.fileMenu) self.menuBar().addMenu(self.viewMenu) self.menuBar().addMenu(self.helpMenu) def updateActions(self): self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) def scaleImage(self, factor, isAbsolute=False): if isAbsolute: self.scaleFactor = factor else: self.scaleFactor *= factor self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAct.setEnabled(self.scaleFactor < 3.0) self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue(int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep() / 2))) def parse_config(self): # config file name configFileName = 'mySolutionBrowserConfig.ini' filePath = os.path.dirname(os.path.realpath(__file__)) configFilePath = os.path.join(filePath, configFileName) # check if exists if os.path.isfile(configFilePath): print('loading config file') self.load_config_file(configFilePath) else: print('creating new config file') self.create_config_file(configFilePath) print('load just created config file') self.load_config_file(configFilePath) def create_config_file(self, configFilePath): # create config parser: config = configparser.ConfigParser(allow_no_value=True) # window settings config.add_section('WINDOW') config.set('WINDOW', 'hsize', '3600') config.set('WINDOW', 'vsize', '1600') config.set('WINDOW', 'start_maximized', 'yes') # data settings config.add_section('DATA') config.set('DATA', 'base_folder', 'C:\\Users\\rickw\\OneDrive\\Studie\\BMD_Master\\Internship_ImPhys\\EMech_waves\\mechanical_model\\model\\data') config.set('DATA', 'parlist_filename', 'parlist_sim.csv') config.set('DATA', 'default_set') # AHK settings config.add_section('AHK') config.set('AHK', 'executable_path') # Writing our configuration file to with open(configFilePath, 'w') as configfile: config.write(configfile) def load_config_file(self, configFilePath): # create config parser: config = configparser.ConfigParser(allow_no_value=True) # load config file config.read(configFilePath) # Window section self.hsize = config.getint('WINDOW', 'hsize') self.vsize = config.getint('WINDOW', 'vsize') self.isStartMaximized = config.getboolean('WINDOW', 'start_maximized') # data section self.base_folder = config.get('DATA', 'base_folder') self.parlist_filename = config.get('DATA', 'parlist_filename') self.default_set = config.get('DATA', 'default_set') # AHK section self.ahk_executable_path = config.get('AHK', 'executable_path')