class SandyScreen(QWidget): def __init__(self, root): QWidget.__init__(self) self.root = root # Setting state vars self.app_running = True self.ctrl_down = False self.in_fullscreen = False self.in_menu = False self.on_pause = True # Setting animation vars self.timer_delay = 10 self.phase = 1 self.delta_per_tick = 1 / 30 self.xpmsz = None self.geo = [0, 0, 0, 0] # Creating timer and sandpile self.timer = QTimer(self) self.seed = None self.piletype = None self.sandpile = None self.reroll_pile() # Generating pause icon self.pause_icon = Image.new('RGBA', size=(60, 60)) for i in range(60): for j in range(60): self.pause_icon.putpixel( (j, i), (255, 255, 255, 0 if 20 < j < 40 else 100)) # Initialising UI and running self.init_ui() self.show() def init_ui(self): self.setMinimumSize(600, 400) self.setWindowTitle('Sandy Screen') # Menu widget self.menu = SandyMenu(self) # Sandbox container widget and its background and canvas label stack self.sandbox = QWidget(self) self.sandbox.setGeometry(0, 0, 600, 400) self.sandbox_bg = QLabel(self.sandbox) self.sandbox_bg.setGeometry(0, 0, 600, 400) self.sandbox_bg.setPixmap( QPixmap(['1 1 1 1', '1 c #000000', '1']).scaled(self.sandbox_bg.size())) self.canvases = [QLabel(self.sandbox), QLabel(self.sandbox)] for c in self.canvases: c.setAlignment(Qt.AlignCenter) c.setGeometry(0, 0, 600, 400) self.canvases[1].setPixmap(QPixmap(generate_xpm(self.sandpile)[0])) self.sandpile.topple_step() self.canvases[0].setPixmap(QPixmap(generate_xpm(self.sandpile)[0])) # Opacity effect on the upper frame label self.opeff = QGraphicsOpacityEffect(self.canvases[-1]) self.opeff.setOpacity(1) self.canvases[-1].setGraphicsEffect(self.opeff) # Main stack layout self.layout = QStackedLayout(self) self.layout.addWidget(self.sandbox) self.layout.addWidget(self.menu) # Overlay pause icon label self.pause_label = QLabel(self) self.pause_label.setAlignment(Qt.AlignCenter) self.pause_label.setPixmap(QPixmap(ImageQt.ImageQt(self.pause_icon))) def restart_pile(self): del self.sandpile self.sandpile = self.piletype(copy.deepcopy(self.seed), frozen=True, timed=False, vocal=False) print('restart called') def reroll_pile(self, piletype=None, seed=None): if seed: self.seed = seed else: v = random.randint(8, 33) self.seed = [[v for j in range(_X)] for i in range(_Y)] if piletype: self.piletype = piletype else: v = random.randint(0, 3) self.piletype = [t4f, t6hf, t6vf, t8f][v] self.sandpile = self.piletype(copy.deepcopy(self.seed), frozen=True, timed=False, vocal=False) def run(self): self.root.exec_() def closeEvent(self, event): self.timer.stop() self.app_running = False def keyPressEvent(self, event): k = event.key() if k == 16777236: self.update_sandbox(1) # if k == _CTRL: self.ctrl_down = True if self.ctrl_down: if k == _M: self.toggle_menu() # Menu toggled on Ctrl+M elif k == _R: self.reroll_pile() else: if k == _F11: self.toggle_fullscreen() elif k == _SPACE and not self.in_menu: self.toggle_pause() def keyReleaseEvent(self, event): if event.key() == _CTRL: self.ctrl_down = False def resizeEvent(self, event): self.pause_label.setGeometry(self.rect()) sbg = self.sandbox.geometry() sbs = self.sandbox.size() self.sandbox_bg.setGeometry(sbg) self.sandbox_bg.setPixmap(self.sandbox_bg.pixmap().scaled( self.sandbox_bg.size())) for c in self.canvases: c.setGeometry(sbg) c.setPixmap(c.pixmap().scaled(sbs, Qt.KeepAspectRatio)) ### Fix this. def toggle_fullscreen(self): self.in_fullscreen = not self.in_fullscreen self.showFullScreen() if self.in_fullscreen else self.showNormal() def toggle_menu(self): self.in_menu = not self.in_menu if self.in_menu: self.layout.setCurrentIndex(1) self.pause_label.setVisible(False) self.on_pause = True self.root.restoreOverrideCursor() else: self.layout.setCurrentIndex(0) if self.on_pause: self.pause_label.raise_() self.pause_label.setVisible(True) else: self.root.setOverrideCursor(Qt.BlankCursor) self.resizeEvent(None) def toggle_pause(self): self.on_pause = not self.on_pause if self.on_pause: self.timer.stop() self.pause_label.raise_() self.pause_label.setVisible(True) self.root.restoreOverrideCursor() else: self.pause_label.setVisible(False) self.timer.singleShot(self.timer_delay, lambda: self.update_sandbox(0)) self.root.setOverrideCursor(Qt.BlankCursor) def update_sandbox(self, mode): if not self.in_menu: if mode == self.on_pause: self.phase -= self.delta_per_tick if self.phase <= 0: self.canvases[-1].setPixmap(self.canvases[0].pixmap()) self.phase = 1 self.opeff.setOpacity(self.phase) if not self.sandpile.topple_step() and not self.on_pause: self.toggle_pause() xpm, sz = generate_xpm(self.sandpile) self.canvases[0].setPixmap( QPixmap(xpm).scaled(self.sandbox.size(), Qt.KeepAspectRatio)) else: self.opeff.setOpacity( math.pow(math.sin(math.pi * self.phase / 2), 2)) if mode == 0 and self.app_running: self.timer.singleShot(self.timer_delay, lambda: self.update_sandbox(0))
class Grabber(QWidget): dirty = True def __init__(self, x, y, w, h, inputs_queue, update_position_queue, update_position_lock): QWidget.__init__(self) self.setWindowTitle('ReLuu FaceReader') self.x = x self.y = y self.w = w self.h = h self.inputs_queue = inputs_queue self.update_position_queue = update_position_queue self.update_position_lock = update_position_lock self.setGeometry(self.x, self.y, self.w, self.h) self.acceptDrops() # Window stays on top, and the other 2 combine to remove the min/close/expand buttons self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.CustomizeWindowHint | Qt.WindowTitleHint) layout = QVBoxLayout() self.setLayout(layout) # limit widget AND layout margins layout.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # create a "placeholder" widget for the screen grab geometry self.grabWidget = QWidget() self.grabWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) layout.addWidget(self.grabWidget) self.show() def updateMask(self): # get the *whole* window geometry, including its titlebar and borders frameRect = self.frameGeometry() # get the grabWidget geometry and remap it to global coordinates grabGeometry = self.grabWidget.geometry() grabGeometry.moveTopLeft(self.grabWidget.mapToGlobal(QPoint(0, 0))) self.update_position_lock.acquire() #Sends info to the input queue x1 = grabGeometry.getRect()[0] y1 = grabGeometry.getRect()[1] width = grabGeometry.getRect()[2] height = grabGeometry.getRect()[3] message = f"UPDATE {x1} {y1} {width} {height}" # if not self.update_position_queue.empty(): # _ = self.update_position_queue.get() # self.update_position_queue.put(message) # else: # self.update_position_queue.put(message) self.inputs_queue.put(message) self.update_position_lock.release() # get the actual margins between the grabWidget and the window margins left = frameRect.left() - grabGeometry.left() top = frameRect.top() - grabGeometry.top() right = frameRect.right() - grabGeometry.right() bottom = frameRect.bottom() - grabGeometry.bottom() # reset the geometries to get "0-point" rectangles for the mask frameRect.moveTopLeft(QPoint(0, 0)) grabGeometry.moveTopLeft(QPoint(0, 0)) # create the base mask region, adjusted to the margins between the # grabWidget and the window as computed above region = QRegion(frameRect.adjusted(left, top, right, bottom)) # "subtract" the grabWidget rectangle to get a mask that only contains # the window titlebar, margins and panel region -= QRegion(grabGeometry) self.setMask(region) # update the grab size according to grabWidget geometry # self.widthLabel.setText(str(self.grabWidget.width())) # self.heightLabel.setText(str(self.grabWidget.height())) def resizeEvent(self, event): super(Grabber, self).resizeEvent(event) # the first resizeEvent is called *before* any first-time showEvent and # paintEvent, there's no need to update the mask until then; see below if not self.dirty: # print("rezise event") self.updateMask() def moveEvent(self, event): super(Grabber, self).moveEvent(event) if not self.dirty: # print("Moving it") self.updateMask() def paintEvent(self, event): super(Grabber, self).paintEvent(event) # on Linux the frameGeometry is actually updated "sometime" after show() # is called; on Windows and MacOS it *should* happen as soon as the first # non-spontaneous showEvent is called (programmatically called: showEvent # is also called whenever a window is restored after it has been # minimized); we can assume that all that has already happened as soon as # the first paintEvent is called; before then the window is flagged as # "dirty", meaning that there's no need to update its mask yet. # Once paintEvent has been called the first time, the geometries should # have been already updated, we can mark the geometries "clean" and then # actually apply the mask. if self.dirty: self.updateMask() self.dirty = False def close_window(self): self.close()
class MediaText(Media): def __init__(self, media, parent_widget): super(MediaText, self).__init__(media, parent_widget) self.widget = QWidget(parent_widget) self.process = QProcess(self.widget) self.process.setObjectName('%s-process' % self.objectName()) self.std_out = [] self.errors = [] self.stopping = False self.mute = False self.widget.setGeometry(media['geometry']) self.connect(self.process, SIGNAL('error()'), self.process_error) self.connect(self.process, SIGNAL('finished()'), self.process_finished) self.connect(self.process, SIGNAL('started()'), self.process_started) self.set_default_widget_prop() self.stop_timer = QTimer(self) self.stop_timer.setSingleShot(True) self.stop_timer.setInterval(1000) self.stop_timer.timeout.connect(self.process_timeout) self.rect = self.widget.geometry() @Slot() def process_timeout(self): os.kill(self.process.pid(), signal.SIGTERM) self.stopping = False if not self.is_started(): self.started_signal.emit() super(MediaText, self).stop() @Slot(object) def process_error(self, err): print('---- process error ----') self.errors.append(err) self.stop() @Slot() def process_finished(self): self.stop() @Slot() def process_started(self): self.stop_timer.stop() if float(self.duration) > 0: self.play_timer.setInterval(int(float(self.duration) * 1000)) self.play_timer.start() self.started_signal.emit() pass @Slot() def play(self): self.finished = 0 self.widget.show() self.widget.raise_() #---- kong ---- path = f'file:///home/pi/rdtone/urd/content/{self.layout_id}_{self.region_id}_{self.id}.html' print(path) l = str(self.rect.left()) t = str(self.rect.top()) w = str(self.rect.width()) h = str(self.rect.height()) s = f'--window-size={w},{h}' p = f'--window-position={l},{t}' args = [ '--kiosk', s, p, path #l, t, w, h, path ] self.process.start('chromium-browser', args) #self.process.start('./xWeb', args) self.stop_timer.start() #---- @Slot() def stop(self, delete_widget=False): #---- kong ---- if not self.widget: return False if self.stopping or self.is_finished(): return False self.stop_timer.start() self.stopping = True if self.process.state() == QProcess.ProcessState.Running: #---- kill process ---- os.system('pkill chromium') #os.system('pkill xWeb') #---- self.process.waitForFinished() self.process.close() super(MediaText, self).stop(delete_widget) self.stopping = False self.stop_timer.stop() return True
class WebMediaView(MediaView): def __init__(self, media, parent): super(WebMediaView, self).__init__(media, parent) self.widget = QWidget(parent) self.process = QProcess(self.widget) self.process.setObjectName('%s-process' % self.objectName()) self.std_out = [] self.errors = [] self.stopping = False self.mute = False self.widget.setGeometry(media['geometry']) self.connect(self.process, SIGNAL('error()'), self.process_error) self.connect(self.process, SIGNAL('finished()'), self.process_finished) self.connect(self.process, SIGNAL('started()'), self.process_started) self.set_default_widget_prop() self.stop_timer = QTimer(self) self.stop_timer.setSingleShot(True) self.stop_timer.setInterval(1000) self.stop_timer.timeout.connect(self.process_timeout) self.rect = self.widget.geometry() @Slot() def process_timeout(self): os.kill(self.process.pid(), signal.SIGTERM) self.stopping = False if not self.is_started(): self.started_signal.emit() super(WebMediaView, self).stop() @Slot(object) def process_error(self, err): print('---- process error ----') self.errors.append(err) self.stop() @Slot() def process_finished(self): self.stop() @Slot() def process_started(self): self.stop_timer.stop() if float(self.duration) > 0: self.play_timer.setInterval(int(float(self.duration) * 1000)) self.play_timer.start() self.started_signal.emit() pass @Slot() def play(self): self.finished = 0 self.widget.show() self.widget.raise_() #---- kong ---- url = self.options['uri'] args = [ str(self.rect.left()), str(self.rect.top()), str(self.rect.width()), str(self.rect.height()), QUrl.fromPercentEncoding(QByteArray(url.encode('utf-8'))) ] #self.process.start('dist/web.exe', args) # for windows #self.process.start('./dist/web', args) # for RPi self.stop_timer.start() #---- @Slot() def stop(self, delete_widget=False): #---- kong ---- if not self.widget: return False if self.stopping or self.is_finished(): return False self.stop_timer.start() self.stopping = True if self.process.state() == QProcess.ProcessState.Running: #---- kill process ---- self.process.terminate() # for windows self.process.kill() # for linux #os.system('pkill web') # for RPi #---- self.process.waitForFinished() self.process.close() super(WebMediaView, self).stop(delete_widget) self.stopping = False self.stop_timer.stop() return True
class ImageBox(QWidget): dirty = True def __init__(self, start_box_x1, start_box_y1, start_box_x2, start_box_y2, inputs_queue, update_position_lock): super(ImageBox, self).__init__() self.setWindowTitle('ReLuu') cur_dir = os.getcwd() # os.chdir(sys._MEIPASS) icon = QIcon("auxiliary_files\\logo_transparent_background.png") self.setWindowIcon(icon) os.chdir(cur_dir) self.start_box_x1 = start_box_x1 self.start_box_y1 = start_box_y1 self.start_box_x2 = start_box_x2 self.start_box_y2 = start_box_y2 self.inputs_queue = inputs_queue self.update_position_lock = update_position_lock self.setGeometry(self.start_box_x1, self.start_box_y1, self.start_box_x2, self.start_box_y2) self.acceptDrops() # Window stays on top, and the other 2 combine to remove the min/close/expand buttons self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.CustomizeWindowHint | Qt.WindowTitleHint) layout = QVBoxLayout() self.setLayout(layout) # limit widget AND layout margins layout.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # create a "placeholder" widget for the screen grab geometry self.grabWidget = QWidget() self.grabWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) layout.addWidget(self.grabWidget) self.show() def updateMask(self): # get the *whole* window geometry, including its titlebar and borders frameRect = self.frameGeometry() # get the grabWidget geometry and remap it to global coordinates grabGeometry = self.grabWidget.geometry() grabGeometry.moveTopLeft(self.grabWidget.mapToGlobal(QPoint(0, 0))) self.update_position_lock.acquire() #Sends info to the input queue x1 = grabGeometry.getRect()[0] y1 = grabGeometry.getRect()[1] width = grabGeometry.getRect()[2] height = grabGeometry.getRect()[3] message = f"UPDATE {x1} {y1} {width} {height}" self.inputs_queue.put(message) self.update_position_lock.release() # get the actual margins between the grabWidget and the window margins left = frameRect.left() - grabGeometry.left() top = frameRect.top() - grabGeometry.top() right = frameRect.right() - grabGeometry.right() bottom = frameRect.bottom() - grabGeometry.bottom() # reset the geometries to get "0-point" rectangles for the mask frameRect.moveTopLeft(QPoint(0, 0)) grabGeometry.moveTopLeft(QPoint(0, 0)) # create the base mask region, adjusted to the margins between the # grabWidget and the window as computed above region = QRegion(frameRect.adjusted(left, top, right, bottom)) # "subtract" the grabWidget rectangle to get a mask that only contains # the window titlebar, margins and panel region -= QRegion(grabGeometry) self.setMask(region) # update the grab size according to grabWidget geometry # self.widthLabel.setText(str(self.grabWidget.width())) # self.heightLabel.setText(str(self.grabWidget.height())) def resizeEvent(self, event): super(ImageBox, self).resizeEvent(event) # the first resizeEvent is called *before* any first-time showEvent and # paintEvent, there's no need to update the mask until then; see below if not self.dirty: self.updateMask() def moveEvent(self, event): super(ImageBox, self).moveEvent(event) if not self.dirty: self.updateMask() def paintEvent(self, event): super(ImageBox, self).paintEvent(event) if self.dirty: self.updateMask() self.dirty = False def close_window(self): self.close()
def show_error(self, scroll_view: QWidget, txt: str): if self.error_widget is not None: self.error_widget.hide() del self.error_widget geometry = scroll_view.geometry() self.error_widget = QTextEdit(parent=scroll_view) self.error_widget.setGeometry(25, geometry.height() - 85, geometry.width() - 50, 60) #25, 400, 732, 62) self.error_widget.setStyleSheet( 'background-color: rgb(240, 60, 60); border-radius: 10px;') self.error_widget.setHtml(txt) self.error_widget.setReadOnly(True) self.error_widget.show()