From d7f3a7daea89b867da0a1710a2435e08983cd014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Sat, 10 May 2014 14:52:21 +0200 Subject: [PATCH 1/7] changed from ffmpeg grabbing to internal grabbing and added a async http server --- texter/texter/main.py | 372 +++++++++++++++++++++++++++++------------- 1 file changed, 256 insertions(+), 116 deletions(-) diff --git a/texter/texter/main.py b/texter/texter/main.py index a17c363..84ab185 100644 --- a/texter/texter/main.py +++ b/texter/texter/main.py @@ -1,38 +1,49 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# This file is part of texter package +# +# texter is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# texter is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with texter. If not, see . +# +# Copyright (C) 2014 Stefan Kögl + +from __future__ import absolute_import + + import cPickle import os.path import re -import subprocess -import sys -from math import pow - -from operator import itemgetter from PyQt4 import QtCore, QtGui +from PyQt4.QtCore import QBuffer, QByteArray, QIODevice +from PyQt4.QtGui import QPixmap -from PyKDE4.kdecore import ki18n, KCmdLineArgs, KAboutData -from PyKDE4.kdeui import KDialog, KActionCollection, KRichTextWidget, KComboBox, KPushButton, KRichTextWidget, KMainWindow, KToolBar, KApplication, KAction, KToolBarSpacerAction, KSelectAction, KToggleAction, KShortcut +from PyKDE4.kdeui import (KDialog, KActionCollection, KRichTextWidget, + KRichTextWidget, KMainWindow, KToolBar, KAction, KToolBarSpacerAction, + KSelectAction, KToggleAction, KShortcut) -from texter_ui import Ui_MainWindow, _fromUtf8 -from text_sorter_ui import Ui_TextSorterDialog -from text_model import TextModel +from PyQt4.QtNetwork import QTcpServer, QTcpSocket -appName = "texter" -catalog = "448texter" -programName = ki18n("4.48 Psychose Texter") -version = "0.1" +from chaosc.argparser_groups import ArgParser +from chaosc.lib import resolve_host -aboutData = KAboutData(appName, catalog, programName, version) +from texter.texter_ui import Ui_MainWindow, _fromUtf8 +from texter.text_sorter_ui import Ui_TextSorterDialog +from texter.text_model import TextModel -KCmdLineArgs.init (sys.argv, aboutData) - -app = KApplication() - -for path in QtGui.QIcon.themeSearchPaths(): - print "%s/%s" % (path, QtGui.QIcon.themeName()) +app = QtGui.QApplication([]) # NOTE: if the QIcon.fromTheme method does not find any icons, you can use @@ -40,21 +51,155 @@ for path in QtGui.QIcon.themeSearchPaths(): # in your local icon directory: # ln -s /your/icon/theme/directory $HOME/.icons/hicolor +def get_preview_text(text): + return re.sub(" +", " ", text.replace("\n", " ")).strip()[:20] + +class MjpegStreamingServer(QTcpServer): + + def __init__(self, server_address, parent=None): + super(MjpegStreamingServer, self).__init__(parent) + self.server_address = server_address + self.newConnection.connect(self.start_streaming) + self.widget = parent + self.sockets = list() + self.img_data = None + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.render_image) + self.timer.start(80) + self.stream_clients = list() + self.regex = re.compile("^GET /(\w+?)\.(\w+?) HTTP/(\d+\.\d+)$") + self.html_map = dict() + self.coords = parent.live_text_rect() + + def handle_request(self): + sock = self.sender() + sock_id = id(sock) + print "handle_request", sock + if sock.state() in (QTcpSocket.UnconnectedState, QTcpSocket.ClosingState): + print "connection closed" + self.sockets.remove(sock) + sock.deleteLater() + return + + client_data = str(sock.readAll()) + print "request", repr(client_data) + line = client_data.split("\r\n")[0] + print "first line", repr(line) + try: + resource, ext, http_version = self.regex.match(line).groups() + print "resource, ext, http_version", resource, ext, http_version + except AttributeError: + print "regex not matched" + sock.write("HTTP/1.1 404 Not Found\r\n") + else: + if ext == "ico": + directory = os.path.dirname(os.path.abspath(__file__)) + data = open(os.path.join(directory, "favicon.ico"), "rb").read() + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data)) + if ext == "html": + directory = os.path.dirname(os.path.abspath(__file__)) + data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id + self.html_map[sock_id] = None + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: text/html;encoding: utf-8\r\n\r\n%s' % data)) + #sock.close() + elif ext == "mjpeg": + try: + _, html_sock_id = resource.split("_", 1) + html_sock_id = int(html_sock_id) + except ValueError: + html_sock_id = None + + if sock not in self.stream_clients: + print "starting streaming..." + if html_sock_id is not None: + self.html_map[html_sock_id] = sock + self.stream_clients.append(sock) + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: multipart/x-mixed-replace; boundary=--2342\r\n\r\n')) + else: + print "not found/handled" + sock.write("HTTP/1.1 404 Not Found\r\n") + self.sockets.remove(sock) + sock.close() + + def remove_stream_client(self): + sock = self.sender() + sock_id = id(sock) + print "remove_stream_client", sock, sock_id + if sock.state() == QTcpSocket.UnconnectedState: + self.sockets.remove(sock) + print "removed sock", sock + sock.close() + try: + self.stream_clients.remove(sock) + except ValueError, error: + print "sock was not in stream_clients", error + + try: + stream_client = self.html_map.pop(sock_id) + except KeyError, error: + print "socket has no child socket" + else: + print "html socket has linked stream socket to remove", stream_client, id(stream_client) + stream_client.close() + try: + self.stream_clients.remove(stream_client) + except ValueError, error: + print "error", error + + try: + self.sockets.remove(stream_client) + except ValueError, error: + print "error", error + + def render_image(self): + if not self.stream_clients: + return + + pixmap = QPixmap.grabWidget(self.widget.live_text, *self.coords) + buf = QBuffer() + buf.open(QIODevice.WriteOnly) + pixmap.save(buf, "JPG", 25) + self.img_data = buf.data() + len_data = len(self.img_data) + array = QByteArray("--2342\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len_data, self.img_data)) + for sock in self.stream_clients: + sock.write(array) + + def start_streaming(self): + while self.hasPendingConnections(): + sock = self.nextPendingConnection() + sock.readyRead.connect(self.handle_request) + sock.disconnected.connect(self.remove_stream_client) + self.sockets.append(sock) + + def stop(self): + for sock in self.sockets: + sock.close() + sock.deleteLater() + for sock in self.stream_clients: + sock.close() + sock.deleteLater() + self.stream_clients = list() + self.sockets = list() + self.html_map = dict() + self.close() + + class TextSorterDialog(QtGui.QWidget, Ui_TextSorterDialog): - def __init__(self, parent = None): + def __init__(self, parent=None): super(TextSorterDialog, self).__init__(parent) self.setupUi(self) self.fill_list() self.text_list.clicked.connect(self.slot_show_text) - self.remove_button.clicked.connect(self.slot_removeItem) + self.remove_button.clicked.connect(self.slot_remove_item) self.move_up_button.clicked.connect(self.slot_text_up) self.move_down_button.clicked.connect(self.slot_text_down) self.text_list.clicked.connect(self.slot_toggle_buttons) self.move_up_button.setEnabled(False) self.move_down_button.setEnabled(False) - + self.model = None def slot_toggle_buttons(self, index): row = index.row() @@ -71,9 +216,9 @@ class TextSorterDialog(QtGui.QWidget, Ui_TextSorterDialog): def fill_list(self): self.model = self.parent().parent().model self.text_list.setModel(self.model) - ix = self.parent().parent().current_index - index = self.model.index(ix, 0) - self.text_list.setCurrentIndex(index) + index = self.parent().parent().current_index + model_index = self.model.index(index, 0) + self.text_list.setCurrentIndex(model_index) def slot_text_up(self): @@ -107,8 +252,7 @@ class TextSorterDialog(QtGui.QWidget, Ui_TextSorterDialog): except IndexError: pass - - def slot_removeItem(self): + def slot_remove_item(self): index = self.text_list.currentIndex().row() self.model.removeRows(index, 1) index = self.model.index(0, 0) @@ -128,7 +272,6 @@ class FadeAnimation(QtCore.QObject): self.current_alpha = 255 self.timer = None - def start_animation(self): print "start_animation" self.animation_started.emit() @@ -141,13 +284,12 @@ class FadeAnimation(QtCore.QObject): self.timer.timeout.connect(self.slot_animate) self.timer.start(100) - def slot_animate(self): print "slot_animate" print "current_alpha", self.current_alpha if self.fade_delta > 0: if self.current_alpha > 0: - self.live_text.setStyleSheet("color:%d, %d, %d;" % (self.current_alpha, self.current_alpha,self.current_alpha)) + self.live_text.setStyleSheet("color:%d, %d, %d;" % (self.current_alpha, self.current_alpha, self.current_alpha)) self.current_alpha -= self.fade_delta else: self.live_text.setStyleSheet("color:black;") @@ -160,7 +302,7 @@ class FadeAnimation(QtCore.QObject): print "animation_finished" else: if self.current_alpha < 255: - self.live_text.setStyleSheet("color:%d,%d, %d;" % (self.current_alpha, self.current_alpha,self.current_alpha)) + self.live_text.setStyleSheet("color:%d,%d, %d;" % (self.current_alpha, self.current_alpha, self.current_alpha)) self.current_alpha -= self.fade_delta else: self.live_text.setStyleSheet("color:white") @@ -214,7 +356,6 @@ class TextAnimation(QtCore.QObject): def slot_animate(self): self.animation_started.emit() - parent = self.parent() if self.it is None: src_root_frame = self.src_document.rootFrame() @@ -272,12 +413,11 @@ class TextAnimation(QtCore.QObject): class MainWindow(KMainWindow, Ui_MainWindow): - def __init__(self, parent=None): + def __init__(self, args, parent=None): super(MainWindow, self).__init__(parent) - + self.args = args self.is_streaming = False - self.ffserver = None - self.ffmpeg = None + self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self) self.live_center_action = None self.preview_center_action = None self.live_size_action = None @@ -307,8 +447,22 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.font = QtGui.QFont("monospace", self.default_size) self.font.setStyleHint(QtGui.QFont.TypeWriter) - - self.create_toolbar() + self.previous_action = None + self.next_action = None + self.publish_action = None + self.auto_publish_action = None + self.save_live_action = None + self.save_preview_action = None + self.save_action = None + self.dialog_widget = None + self.action_collection = None + self.streaming_action = None + self.text_combo = None + self.clear_live_action = None + self.clear_preview_action = None + self.toolbar = None + self.typer_animation_action = None + self.text_editor_action = None #self.preview_text.document().setDefaultFont(self.font) self.preview_text.setFont(self.font) @@ -322,48 +476,28 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.live_editor_collection = KActionCollection(self) self.live_text.createActions(self.live_editor_collection) self.filter_editor_actions() + self.create_toolbar() self.slot_load() + + app.focusChanged.connect(self.focusChanged) + self.start_streaming() + self.get_live_coords() + self.show() - self.save_action.triggered.connect(self.slot_save) - - self.publish_action.triggered.connect(self.slot_publish) - self.clear_live_action.triggered.connect(self.slot_clear_live) - self.clear_preview_action.triggered.connect(self.slot_clear_preview) - self.text_combo.triggered[int].connect(self.slot_load_preview_text) - - app.focusChanged.connect(self.focusChanged) - self.text_editor_action.triggered.connect(self.slot_open_dialog) - self.save_live_action.triggered.connect(self.slot_save_live_text) - self.save_preview_action.triggered.connect(self.slot_save_preview_text) - self.streaming_action.triggered.connect(self.slot_toggle_streaming) - self.auto_publish_action.toggled.connect(self.slot_auto_publish) - self.typer_animation_action.toggled.connect(self.slot_toggle_animation) - self.preview_size_action.triggered[QtGui.QAction].connect(self.slot_preview_font_size) - self.live_size_action.triggered[QtGui.QAction].connect(self.slot_live_font_size) - - #self.fade_action.triggered.connect(self.slot_fade) - self.next_action.triggered.connect(self.slot_next_item) - self.previous_action.triggered.connect(self.slot_previous_item) - - self.getLiveCoords() - print "desktop", app.desktop().availableGeometry() - - - def getLiveCoords(self): + def get_live_coords(self): public_rect = self.live_text.geometry() global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight())) x = global_rect.x() y = global_rect.y() self.statusBar().showMessage("live text editor dimensions: x=%r, y=%r, width=%r, height=%r" % (x, y, global_rect.width(), global_rect.height())) - def getPreviewCoords(self): + def get_preview_coords(self): public_rect = self.preview_text.geometry() global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight())) return global_rect.x(), global_rect.y() - def filter_editor_actions(self): disabled_action_names = [ @@ -391,7 +525,7 @@ class MainWindow(KMainWindow, Ui_MainWindow): for action in self.live_editor_collection.actions(): text = str(action.objectName()) - print "text", text + #print "text", text if text in disabled_action_names: action.setVisible(False) @@ -417,7 +551,6 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.slot_set_preview_defaults() self.slot_set_live_defaults() - def create_toolbar(self): self.toolbar = KToolBar(self, True, True) @@ -534,9 +667,27 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.toolbar.addSeparator() + self.save_action.triggered.connect(self.slot_save) + + self.publish_action.triggered.connect(self.slot_publish) + self.clear_live_action.triggered.connect(self.slot_clear_live) + self.clear_preview_action.triggered.connect(self.slot_clear_preview) + self.text_combo.triggered[int].connect(self.slot_load_preview_text) + self.text_editor_action.triggered.connect(self.slot_open_dialog) + self.save_live_action.triggered.connect(self.slot_save_live_text) + self.save_preview_action.triggered.connect(self.slot_save_preview_text) + self.streaming_action.triggered.connect(self.slot_toggle_streaming) + self.auto_publish_action.toggled.connect(self.slot_auto_publish) + self.typer_animation_action.toggled.connect(self.slot_toggle_animation) + self.preview_size_action.triggered[QtGui.QAction].connect(self.slot_preview_font_size) + self.live_size_action.triggered[QtGui.QAction].connect(self.slot_live_font_size) + + #self.fade_action.triggered.connect(self.slot_fade) + self.next_action.triggered.connect(self.slot_next_item) + self.previous_action.triggered.connect(self.slot_previous_item) + self.streaming_action.setChecked(True) def closeEvent(self, event): - self.stop_streaming() if self.db_dirty: self.dialog = KDialog(self) self.dialog.setCaption("4.48 texter - text db not saved") @@ -546,20 +697,15 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.dialog.okClicked.connect(self.slot_save) self.dialog.exec_() + def live_text_rect(self): + return 3, 3, 768, 576 + def stop_streaming(self): self.is_streaming = False - if self.ffmpeg is not None: - self.ffmpeg.kill() - self.ffmpeg = None - if self.ffserver is not None: - self.ffserver.kill() - self.ffserver = None + self.http_server.stop() def start_streaming(self): - public_rect = self.live_text.geometry() - global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight())) - self.ffserver = subprocess.Popen("ffserver -f /etc/ffserver.conf", shell=True, close_fds=True) - self.ffmpeg = subprocess.Popen("ffmpeg -f x11grab -show_region 1 -s 768x576 -r 30 -i :0.0+%d,%d -vcodec mjpeg -pix_fmt yuvj444p -r 30 -aspect 4:3 http://localhost:8090/webcam.ffm" % (global_rect.x()+3, global_rect.y()+3), shell=True, close_fds=True) + self.http_server.listen(port=9009) self.is_streaming = True def focusChanged(self, old, new): @@ -572,16 +718,11 @@ class MainWindow(KMainWindow, Ui_MainWindow): def custom_clear(self, cursor): cursor.beginEditBlock() - cursor.movePosition(QtGui.QTextCursor.Start); - cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.KeepAnchor); + cursor.movePosition(QtGui.QTextCursor.Start) + cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.endEditBlock() - - def get_preview_text(self, text): - return re.sub(" +", " ", text.replace("\n", " ")).strip()[:20] - - def slot_auto_publish(self, state): self.is_auto_publish = bool(state) @@ -589,11 +730,10 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.is_animate = bool(state) def slot_toggle_streaming(self): - if self.ffserver is None: - self.start_streaming() - else: + if self.is_streaming: self.stop_streaming() - + else: + self.start_streaming() def slot_next_item(self): try: @@ -603,7 +743,6 @@ class MainWindow(KMainWindow, Ui_MainWindow): except ZeroDivisionError: pass - def slot_previous_item(self): try: self.current = (self.text_combo.currentItem() - 1) % len(self.model.text_db) @@ -612,26 +751,22 @@ class MainWindow(KMainWindow, Ui_MainWindow): except ZeroDivisionError: pass - def slot_publish(self): if self.is_animate: self.animation.start_animation(self.preview_text, self.live_text, 0) else: self.live_text.setTextOrHtml(self.preview_text.textOrHtml()) - def slot_live_font_size(self, action): self.default_size = self.live_size_action.fontSize() self.slot_set_preview_defaults() self.slot_set_live_defaults() - def slot_preview_font_size(self, action): self.default_size = self.preview_size_action.fontSize() self.slot_set_live_defaults() self.slot_set_preview_defaults() - def slot_toggle_publish(self, state=None): if state: @@ -639,7 +774,6 @@ class MainWindow(KMainWindow, Ui_MainWindow): else: self.slot_clear_live() - def slot_set_preview_defaults(self): self.preview_center_action.setChecked(True) self.preview_text.alignCenter() @@ -675,11 +809,11 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.text_combo.clear() current_row = -1 - for ix, list_obj in enumerate(self.model.text_db): + for index, list_obj in enumerate(self.model.text_db): preview, text = list_obj self.text_combo.addAction(preview) if list_obj == self.current_object: - current_row = ix + current_row = index if current_row == -1: current_row = self.current_index @@ -697,7 +831,7 @@ class MainWindow(KMainWindow, Ui_MainWindow): def slot_save_live_text(self): text = self.live_text.toHtml() - preview = self.get_preview_text(unicode(self.live_text.toPlainText())) + preview = get_preview_text(unicode(self.live_text.toPlainText())) if not preview: return old_item = self.model.text_by_preview(preview) @@ -720,7 +854,7 @@ class MainWindow(KMainWindow, Ui_MainWindow): def slot_save_preview_text(self): text = self.preview_text.toHtml() - preview = self.get_preview_text(unicode(self.preview_text.toPlainText())) + preview = get_preview_text(unicode(self.preview_text.toPlainText())) if not preview: return @@ -747,7 +881,6 @@ class MainWindow(KMainWindow, Ui_MainWindow): cPickle.dump(self.model.text_db, f, cPickle.HIGHEST_PROTOCOL) self.db_dirty = False - def slot_open_dialog(self): self.current_index = self.text_combo.currentItem() self.current_object = self.model.text_db[self.current_index] @@ -758,13 +891,8 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.dialog = KDialog(self) self.dialog_widget = TextSorterDialog(self.dialog) self.dialog.setMainWidget(self.dialog_widget) - pos_x, pos_y = self.getPreviewCoords() + pos_x, pos_y = self.get_preview_coords() self.dialog.move(pos_x, 0) - rect = app.desktop().availableGeometry() - global_width = rect.width() - global_height = rect.height() - x = global_width - pos_x - 10 - #self.dialog.setFixedSize(x, global_height-40) self.dialog.okClicked.connect(self.fill_combo_box) self.dialog.exec_() @@ -773,25 +901,37 @@ class MainWindow(KMainWindow, Ui_MainWindow): if not os.path.isdir(path): os.mkdir(path) try: - f = open(os.path.join(path, "texter.db")) + db_file = open(os.path.join(path, "texter.db")) except IOError: return try: - self.model.text_db = [list(i) for i in cPickle.load(f)] - except Exception, e: - print e + self.model.text_db = [list(i) for i in cPickle.load(db_file)] + except ValueError, error: + print error self.fill_combo_box() self.text_combo.setCurrentItem(0) self.slot_load_preview_text(0) - def main(): - window = MainWindow() + arg_parser = ArgParser("dump_grabber") + arg_parser.add_global_group() + client_group = arg_parser.add_client_group() + arg_parser.add_argument(client_group, '-x', "--http_host", default="::", + help='my host, defaults to "::"') + arg_parser.add_argument(client_group, '-X', "--http_port", default=9001, + type=int, help='my port, defaults to 9001') + arg_parser.add_chaosc_group() + arg_parser.add_subscriber_group() + args = arg_parser.finalize() + + args.http_host, args.http_port = resolve_host(args.http_host, args.http_port, args.address_family) + + window = MainWindow(args) app.exec_() -if ( __name__ == '__main__' ): +if __name__ == '__main__': main() From b123160470f6f7c213ab09e086175b8056f73ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Sun, 11 May 2014 12:45:43 +0200 Subject: [PATCH 2/7] added icons and cleaned up code --- texter/setup.py | 4 +- texter/texter/build.sh | 4 +- texter/texter/{texter4.ui => edit_dialog.ui} | 29 +- texter/texter/images/document-new.png | Bin 0 -> 579 bytes texter/texter/images/document-open-data.png | Bin 0 -> 611 bytes texter/texter/images/document-open-recent.png | Bin 0 -> 697 bytes texter/texter/images/document-save.png | Bin 0 -> 563 bytes texter/texter/images/edit-clear.png | Bin 0 -> 802 bytes texter/texter/images/edit-copy.png | Bin 0 -> 485 bytes texter/texter/images/go-next-view-page.png | Bin 0 -> 639 bytes .../texter/images/go-previous-view-page.png | Bin 0 -> 618 bytes texter/texter/images/media-playback-stop.png | Bin 0 -> 499 bytes texter/texter/images/media-record.png | Bin 0 -> 735 bytes texter/texter/images/view-refresh.png | Bin 0 -> 931 bytes texter/texter/index.html | 9 + texter/texter/main.py | 185 ++--- texter/texter/text_model.py | 2 - texter/texter/text_sorter.ui | 157 ---- texter/texter/texter.qrc | 11 + texter/texter/texter.ui | 772 +++--------------- texter/texter/texter2.ui | 395 --------- texter/texter/texter3.ui | 403 --------- texter/texter/texter_rc.py | 548 ++++++++++++- texter/texter/texter_ui.py | 19 +- 24 files changed, 762 insertions(+), 1776 deletions(-) rename texter/texter/{texter4.ui => edit_dialog.ui} (97%) create mode 100644 texter/texter/images/document-new.png create mode 100644 texter/texter/images/document-open-data.png create mode 100644 texter/texter/images/document-open-recent.png create mode 100644 texter/texter/images/document-save.png create mode 100644 texter/texter/images/edit-clear.png create mode 100644 texter/texter/images/edit-copy.png create mode 100644 texter/texter/images/go-next-view-page.png create mode 100644 texter/texter/images/go-previous-view-page.png create mode 100644 texter/texter/images/media-playback-stop.png create mode 100644 texter/texter/images/media-record.png create mode 100644 texter/texter/images/view-refresh.png create mode 100644 texter/texter/index.html delete mode 100644 texter/texter/text_sorter.ui delete mode 100644 texter/texter/texter2.ui delete mode 100644 texter/texter/texter3.ui diff --git a/texter/setup.py b/texter/setup.py index 72c3973..d1879a6 100644 --- a/texter/setup.py +++ b/texter/setup.py @@ -18,7 +18,7 @@ setup( include_package_data = True, package_data = { - "texter" : ["*.ui", "*.qrc", "*.png"]}, + "texter" : ["*.ui", "*.qrc", "*.png", "*.ico", "*.html"]}, exclude_package_data = {'': ['.gitignore']}, @@ -47,7 +47,7 @@ setup( """, # FIXME: add license - license = "LGPL", + license = "GPL", # FIXME: add keywords keywords = "", diff --git a/texter/texter/build.sh b/texter/texter/build.sh index 94ec27b..9b8cbaa 100644 --- a/texter/texter/build.sh +++ b/texter/texter/build.sh @@ -1,2 +1,2 @@ -pyuic4 -o texter_ui.py texter3.ui -pyuic4 -o text_sorter_ui.py texter4.ui +# pyuic4 -o texter_ui.py texter.ui +pyuic4 -o edit_dialog_ui.py edit_dialog.ui diff --git a/texter/texter/texter4.ui b/texter/texter/edit_dialog.ui similarity index 97% rename from texter/texter/texter4.ui rename to texter/texter/edit_dialog.ui index c066dce..7c587ed 100644 --- a/texter/texter/texter4.ui +++ b/texter/texter/edit_dialog.ui @@ -1,7 +1,7 @@ - TextSorterDialog - + EditDialog + 0 @@ -16,6 +16,18 @@ + + + 768 + 576 + + + + + 16777215 + 576 + + Qt::Horizontal @@ -506,19 +518,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - diff --git a/texter/texter/images/document-new.png b/texter/texter/images/document-new.png new file mode 100644 index 0000000000000000000000000000000000000000..8431237bd374e68931a13df798bc0996452f705f GIT binary patch literal 579 zcmV-J0=)f+P)VxXK$hi6FoGM6#-0&DYi+BPO7z*s9d>{0vUq8c z&G}94mG91_zoj3m9VHsT2!GA?={D6)mHkfz06td>m_bcrSPcMDDeJM*+y>fjK<#^t z=uU*oi<@Nt)FU+;02V?7_3H~3cIR1VuFMcTS6l_&FA2GV;y{3lgxXDZuxrZSS;?#* z=QUvpn-9RT!7#-`zX5oG|B7vHE(aR6zHX6znI;PO!UTD_B~KNw&}=sMyujCO=T4U( z2yTeTTYCpkegjtzCeCEM0NlE6vV;ClKoWLfLST%sZMP!Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L0B*Vf0B*Vg*50uf00007bV*G`2iXc1 z4FEa_^Swj>00HAkL_t(|+KrMwXj4%X#=mp#eSeZPjY$KHE1R;S|sUqr28v9=EyPo?@ho;`(4?Z58cfNDJ z`|cx5C>DzgQ55+A2>(X2_E-iEmW%_fwdr&^uK=8zUZAc&`DjKl5V083?RH5@2{xf# zufx)6wa{oZZfpH^wOS397jFi5 zy8anT5<&$iL7?8(0}KxS4&VfsPS9?*Q7V-X1Oe`>-ay$ug<>f2=p_#VfrhchkP|T0 zbSy!$**ubLZxCUWXb=GkuusB-=>*Q&p+^Z4ln?kNa4Z2FHQS$Ku@|)bV8i^}Pk?wI z1MTr9<-BNOX+6fAI3DTDQD!aAbh|I*(;ox%!B(~j4OjpG002ovPDHLkV1g~v0QCR> literal 0 HcmV?d00001 diff --git a/texter/texter/images/document-open-recent.png b/texter/texter/images/document-open-recent.png new file mode 100644 index 0000000000000000000000000000000000000000..681c8dab69865ae9a8c9fc7d38aab32600cf31de GIT binary patch literal 697 zcmV;q0!ICbP)`W%dd#wY` z5Q`uDTsSl5yXP`@1R{bU2u=V#{0pbYfdl7GL1^|uFMr3!&|~DKuS8U`V+kJ-h0%a7 zQW2-`yn(;79s51Nlmbjl9l8*H_2dkIQSb>E9mM!4%cLYzG@njWCYvQGnI_lCAsUaQ zsJy7sz~CTdatn0p;UtMY14SIYw?f2U*lacy5uzYKUc-2NIP$9yr`;>DXKe-_q&PaoZ+PZ+0g)H?iD6tM{u#rSO2wFfucrKY4-Dh^ZdQw! zj=+k`M%Q(gIx4DG2cUz|P+*mD_;j)?!`|G4p$DM}08ubZH?dl+lB%ktX&TjPHDWtb zyTHjPQP^h#+bF4=l_0Z fKxc^(0~US(SI=l_TS_Lz literal 0 HcmV?d00001 diff --git a/texter/texter/images/document-save.png b/texter/texter/images/document-save.png new file mode 100644 index 0000000000000000000000000000000000000000..8072aea3268164eb2028a0cebfe44da82b4f4aca GIT binary patch literal 563 zcmV-30?hr1P)XvKK@k7Ytwo{YB(9F? z;2_0Ku;{F~yQsL?!9iy?1;;|c4%SjZ%{70TwwHKcBH=J-%EN&+{;d zhN(E7P<- zOhP?!y}wyJm`;b*`kqJvqBiX|nL~L2fd*Ux1d!eYF6Tbg5P<9ZC&&uF5~UDVJ~Kya z$B9GKW9yGCk1)gXRIvtRXipPk@<6(R{ z10;&Qkze=csdPHpgO1qUX}3Wtm&>>~JBMXkI4Be#D>XTZ+DDB(A$cT#*xhj)kQiey zO%sM;K-YE1@`TY9(UZv}-m29|6TcYK$LKUNox!$kO4I`&Fl}aA7Py>1;e44jn~%EA z#v7jv1002ovPDHLkV1jI` B^11*3 literal 0 HcmV?d00001 diff --git a/texter/texter/images/edit-clear.png b/texter/texter/images/edit-clear.png new file mode 100644 index 0000000000000000000000000000000000000000..19a1665baf0a3d62cc5f72801c02ee31d832ef03 GIT binary patch literal 802 zcmV+-1Ks?IP)DEsaU~GX3SIsdMei7bh)})pLEh`3!Xi^UM4W+LIZr$zxaOz7mw|A28X|&gK2(h zZfSoyEkX7Ar7SOshe$kJ@wtDv=f<5!bjpH@N4L9nR{6W;bYuBbX#H42w#$q|FGoYkR7TQxAVVC~AgvlcDbQ{K}v zz)cqHtE#WH+tMnPL>x$^K&cL@=%%l{d{cK)X)1KPX3v;gl4lwwgdERi@@M$WfsL{? zP3)eLTi{~|fOrH9%Ru@V9LpV0qngA+;sn5whQ6+jjvrr!>i^mx(;;26*la$T(-0mV zM6EV|5;_BFz$$UjH5GQd1)8dx zW$%Zmk#1*hIjlV+=?;EPa9k=7g`$b#;sWMW{kp}nRc!428shS@9j29Ip{OZjyKMj{ zG9;eH^E^zu)Bdikvh?_!mu@q(n3*v73Z?HG|HJ$KtcH9J10IQl4im gAunrc0{^W1Pl-(m{G3O!W1aTUgGKN%6@}cL`d0sS5o>yped=IE{-7;x28@w z?{(Ng;8?o(vZal2UCV9SDn2q^kz14h;ArvQ8^O9iIQDs;w`*7+VDp{f_ZlWKjr z)#jWRvnDK(uc_Jn=1hKh7{h^v8aJ_m2#-3&dS~D8ALfP$ET*!d=Iaj$ESaLv%HXJJ zadX**`|pc8)DD~|l;Q1I%+aL4pv!&XZJDLtiIt|8wDsLQZsnLs95p=my!g*?kHg2f ze}skYh-*HqEXlpecGB}onN3VDlz0sp?)p^E4K+@`E$^}V!kg~`uY(tgCWzj2SkAUA zhv5~wSpvTuPll7r+MOC)9ESz0WW}p&`9(OEY-(cc;=5QH8+dok<}z*b-~0I2y#2;5 zm%ceYifxa*+WiUnz4IqbQQ=IBY~#^CEhHY#9paTJbKGK|^!~uRnp-kXG?@4ro-by5 zaqMZ<%Gv6Zf0wV?sdn1z0IR9imMNmDb$a0zwKG0V)~~O%Gci?|KU*m}BkiApw!tmN Y4DJ^lh1J@%z&K#=boFyt=akR{0F^SkGXMYp literal 0 HcmV?d00001 diff --git a/texter/texter/images/go-next-view-page.png b/texter/texter/images/go-next-view-page.png new file mode 100644 index 0000000000000000000000000000000000000000..f794c3d9857f5a2890e0fbf8ad1e5fd76b59d758 GIT binary patch literal 639 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~&H|6fVg?4b zHz3R?S88Dj6l5>)^mS#w!OqMjDzk^bVj@tFV1Q4ED^Lm#U}R)OmNhEW?Vm7l^5iK! zef<+AOqB4DmGIP%4wsY-6IV=+8!G^&p&!@Sn_;}l;y!C zOUg^v@RV-~sM%c6x=*2d<)pqN%KcL&O+2A8^Ah*$x%1}DUoant)-GOfooCVF#mknj zSiXD(2wccob)jhI&Yin--`}UnX=3PC4KzYU_Z+Dj{=G{vd z0XfG#T^vIyZrS!!hcgC>xUYAYyX)b}skms*tP2_^mAQCV&AhYo`MK}^73OY!wyv&z z?%wNh?W~Xga~nh*x?ElTJnBu|oV@~zN=j=yLY;2zciORTcKymL+vVIhx=#suHji!V zC!SVy0mcWPwq4z^ZjwS6*QU353wJzu!@;VN87*%9yj;jb;qWPwb4L6OT&+%tv$>X? zZQc6s8}|)~2PJOHF3M+r{Q2UJ|AwMV?Em`LU%xkJ)n$of#dDQKKmIs|YMuJ?@|DV> dnOadZ-r1iuG17|I|1KUB=bo;9F6*2UngAD-C4B$@ literal 0 HcmV?d00001 diff --git a/texter/texter/images/go-previous-view-page.png b/texter/texter/images/go-previous-view-page.png new file mode 100644 index 0000000000000000000000000000000000000000..c78a1b5a64e1b853bbf474b8a4143a0b57958d52 GIT binary patch literal 618 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~&H|6fVg?4b zHz3R?S88Dj6l5>)^mS#w!OqODYgE|F?FZDu9pDq<3X~!q^iP;LdGeH=zWxalCUU!r ziMh*4#Kn6{r?>Bb;Qjk| zpWXw(hYugVdgYVzIfB*gi2!8(f`Sa(mU%!6;{{83ApT9ux@9)2V|Nj5~ ze^WN(5il%-OM?7@83cqyrDar9)vR3n{PXK>-@bF_*aR^vpfqEWx4TQk+pS0XfSjG4 zE{-7*lB@?~^_T-C7%qIiTjw>gXF`VuM`uLu)iTyPvjo;X4;)plUMNe;dS{`2TCbtZ z<=y>*#|sYD^ggk!7wbM5=99K1bI!kgS2urO{YdI_%15i*wn9_h36D0Xm|qiWoG@qq znpvKrj723ipZ;9EqSzFqDtPv#x6k4gTRoHuD>vNKbiU759pq3P^6L5~{dS?A7t^+H zt-qzlk#+t0zrxLD%lz&oocOoz?UN197*f-}ZFuRK@qEGm!}jwEeZRQ7>Hh^qzNf37 J%Q~loCIFKfAb$V= literal 0 HcmV?d00001 diff --git a/texter/texter/images/media-playback-stop.png b/texter/texter/images/media-playback-stop.png new file mode 100644 index 0000000000000000000000000000000000000000..180280e8ba359d2a4389e93284454cf943b27dbd GIT binary patch literal 499 zcmVMzCV_|S* zE^l&Yo9;Xs0004UNklyX+scBW;@{ixB;GHteD? zi4zVk%KMm2d+-vpg@ZO1m$9PhNBs_CwvU6gp6B-T^cklp z%LPGv>rmBAl^Q-iHLBFjq4KSMp<&gV+w9V!%@Z9SXtBdK^=89D1o6}c7u@qo%qL%H z-gxGm4LpU2vNw&#BOe&P>Cq!4LGwb5O`~jC4;H!Q9m5r89H7`jk)mm{$oi2~!WHh& zq+HUVf=iBBAfd||l~Br&N1K?GJ(kHa#W>SIO3XgxNJ>$aM>IXMj535U#3;~1b3iGQ zVp#4kxJ)p@Fe6O#7nUi8QVbk!=#sF+43ms8$qbOt!J&90^LD|k@{A_sUnv@of|<7k zaaER6UeF|@gCDNR;wnUtIm>NkSffps7)?xvDjD47oYjZrT8_$_3?+8hV}T5L<0#h} p&_s}e?F9KS7v-XS=mdoU?jMapX0!E+{+R#(002ovPDHLkV1o6$+0Fm} literal 0 HcmV?d00001 diff --git a/texter/texter/images/media-record.png b/texter/texter/images/media-record.png new file mode 100644 index 0000000000000000000000000000000000000000..1dc4efd4ef3a107b44c33c26cb1117bc20586cb3 GIT binary patch literal 735 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJbFq_W2nPqp?T7vkfLzW3kH}&M z2FBeW%xLxI@gtz1WQl7;NpOBzNqJ&XDnogBxn5>oc5!lIL8@MUQTpt6Hc~* zfr5#Ni9n56Sy@1pxw*MO6AB9pfhtQ&OUug2fNlla4zv_#2+-b+j*kBR{s|K%%$YN1 z$BrGlcJ11?Z{N9d=Pq2haQX7(D_5>uy?XT;3&%B6%bRSRH*em&C9ibL)#LW<+jl}E z?%ut7&)n+1uEBjvoBOs7_Y;yIa&SGgw0`vH(PJ68=iEHcEv;X?c=7V(%U7>nz1BB) zJ#WDqAbRuW?c$|xSFC#b_U-%k?>{V9_+iJ+k9+rj{P^+Hr%#_hfBwqP|LxniU%!6+ z{{8#UuU~)u{Q3L$@4sKafFW6LdsYM(`idn%e!&ckK%fQ$K;IRD{nEG<2)1o|4g^30 zzD?sg>%TN_vv}raHT~$3@AZo7_g;FSlO`+?`!mwE zogpDR%%iBAf#brjbb+Sq4koAH;!(LJF7Y zJnmO@Q~C4^jn!w)t?s^M9$~%n^EAEPZq@t=vHLF1)tqv0+tRSg*n900IxL<&EGU}t zE_;?z$MKuLs=qH;=IV4tVZ!ru&pK4LvmUUIT%7Y`wYAV&pcffDUHx3vIVCg!0Gv{2 ACjbBd literal 0 HcmV?d00001 diff --git a/texter/texter/images/view-refresh.png b/texter/texter/images/view-refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..86b6f82c1ddd53740bd95cd9a252d6d0341b0656 GIT binary patch literal 931 zcmV;U16=%xP)4TMNw)Y6{994!H+hHY0`A;By%&J`*F@a z_jpGPX3-x!yYsx~<$2$ij4{{`Jx5hAT(G*VCr2tHljW{5TiIM$s&CaVL*qm9nfdd- zcS8Vpr@)?(f$&OswEV{T6X)GZe+7emgRq!|AP8{d_6^Kjor$;Cwl5*WkEKfSlO#yu zPJw^T%%N|mPMw-MGx?NYHpBhUQse5v74_@-FLd$gm-u@hzYC5WVtBPS_VCn+vByLZ z2B478Xf&{Kdjn($)!{0<9uFNRj@HCPwT8?XFc5hD`-A-hJ(rFi85fOa13J~1|9KvN zto#l~3wXAND|6qYw^GK*<0sKu>;(*984HXUI1yf`l!lyk+(t)>*B9}3ebW?NAw#Jy zCz;Yd6K9)o!=7E8rRni$xW)xZf<*-G_~MMwpzY>1c7t7P+**h4i)c4W9_=KacQ^?P z&zMJu9yojuE#5+OFG8>rfHD{^99E09LK+09->t*a3~i~t-Wyk!=k|#r^F*(Q^iG1c z;3iskS}^SlX{^5_|ALTN+Y;b_aukFjNU3M_Om=qPZnywS0v3Uc&T?sEl=;-Om`wCF zgehVXLzgwoDB!r(bSGT=&ml(i5lv8^u4P;dTdKS0#K_^P%)W4K@i-2oggzka&QLB2 zhnCn+?0NbeMC91~Txr;yONX-4iK5lGlx|wfY>O_^CgE8;twq17uc8msD$B!Kj$-w?zax~(J9FoG0Riq43dd}+ zCK$>7OcZ>(+U3~eCEFiM7W8ZJR(b?EdprA1V=Mfo=cb$VFb}Q}paNm{V2qtZ5t@TB zTKx%%+^I&_<>{~~mjHZ+oc+f{0AB7s;J)RJa<4L0Wbv^S?XrY*i{b8E+vsaq7~WHF z0(d*`Tq5Uw=X@Bzi@Bb#7#%kTlo_)C;D=oA=f-vH;~!CSYaU5(-z)$C002ovPDHLk FV1l!GvY-F} literal 0 HcmV?d00001 diff --git a/texter/texter/index.html b/texter/texter/index.html new file mode 100644 index 0000000..df3af71 --- /dev/null +++ b/texter/texter/index.html @@ -0,0 +1,9 @@ + + + + + + + Smiley face + + diff --git a/texter/texter/main.py b/texter/texter/main.py index 84ab185..38e4107 100644 --- a/texter/texter/main.py +++ b/texter/texter/main.py @@ -37,10 +37,10 @@ from PyKDE4.kdeui import (KDialog, KActionCollection, KRichTextWidget, from PyQt4.QtNetwork import QTcpServer, QTcpSocket from chaosc.argparser_groups import ArgParser -from chaosc.lib import resolve_host +from chaosc.lib import resolve_host, logger from texter.texter_ui import Ui_MainWindow, _fromUtf8 -from texter.text_sorter_ui import Ui_TextSorterDialog +from texter.edit_dialog_ui import Ui_EditDialog from texter.text_model import TextModel app = QtGui.QApplication([]) @@ -73,30 +73,30 @@ class MjpegStreamingServer(QTcpServer): def handle_request(self): sock = self.sender() + logger.info("handle_request: %s %d", sock.peerAddress(), sock.peerPort()) sock_id = id(sock) - print "handle_request", sock if sock.state() in (QTcpSocket.UnconnectedState, QTcpSocket.ClosingState): - print "connection closed" + logger.info("connection closed") self.sockets.remove(sock) sock.deleteLater() return client_data = str(sock.readAll()) - print "request", repr(client_data) + logger.info("request %r", client_data) line = client_data.split("\r\n")[0] - print "first line", repr(line) + logger.info("first line: %r", line) try: resource, ext, http_version = self.regex.match(line).groups() - print "resource, ext, http_version", resource, ext, http_version + logger.info("resource=%r, ext=%r, http_version=%r", resource, ext, http_version) except AttributeError: - print "regex not matched" + loggging.info("no matching request - sending 404 not found") sock.write("HTTP/1.1 404 Not Found\r\n") else: if ext == "ico": directory = os.path.dirname(os.path.abspath(__file__)) data = open(os.path.join(directory, "favicon.ico"), "rb").read() sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data)) - if ext == "html": + elif ext == "html": directory = os.path.dirname(os.path.abspath(__file__)) data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id self.html_map[sock_id] = None @@ -110,46 +110,50 @@ class MjpegStreamingServer(QTcpServer): html_sock_id = None if sock not in self.stream_clients: - print "starting streaming..." + logger.info("starting streaming...") if html_sock_id is not None: self.html_map[html_sock_id] = sock self.stream_clients.append(sock) sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: multipart/x-mixed-replace; boundary=--2342\r\n\r\n')) else: - print "not found/handled" + logger.error("request not found/handled - sending 404 not found") sock.write("HTTP/1.1 404 Not Found\r\n") - self.sockets.remove(sock) - sock.close() + def remove_stream_client(self): - sock = self.sender() + try: + sock = self.sender() + except RuntimeError: + return sock_id = id(sock) - print "remove_stream_client", sock, sock_id + logger.info("remove_stream_client: sock=%r, sock_id=%r", sock, sock_id) if sock.state() == QTcpSocket.UnconnectedState: + sock.disconnected.disconnect(self.remove_stream_client) self.sockets.remove(sock) - print "removed sock", sock + logger.info("removed sock_id=%r", sock_id) sock.close() try: self.stream_clients.remove(sock) - except ValueError, error: - print "sock was not in stream_clients", error + except ValueError: + pass try: stream_client = self.html_map.pop(sock_id) - except KeyError, error: - print "socket has no child socket" + except KeyError: + logger.info("socket has no child socket") else: - print "html socket has linked stream socket to remove", stream_client, id(stream_client) stream_client.close() try: self.stream_clients.remove(stream_client) - except ValueError, error: - print "error", error + logger.info("removed stream_client=%r", id(stream_client)) + except ValueError: + pass try: self.sockets.remove(stream_client) - except ValueError, error: - print "error", error + logger.info("removed child sock_id=%r", id(stream_client)) + except ValueError: + pass def render_image(self): if not self.stream_clients: @@ -168,6 +172,7 @@ class MjpegStreamingServer(QTcpServer): def start_streaming(self): while self.hasPendingConnections(): sock = self.nextPendingConnection() + logger.info("new connection=%r", id(sock)) sock.readyRead.connect(self.handle_request) sock.disconnected.connect(self.remove_stream_client) self.sockets.append(sock) @@ -185,11 +190,12 @@ class MjpegStreamingServer(QTcpServer): self.close() -class TextSorterDialog(QtGui.QWidget, Ui_TextSorterDialog): +class EditDialog(QtGui.QWidget, Ui_EditDialog): def __init__(self, parent=None): - super(TextSorterDialog, self).__init__(parent) + super(EditDialog, self).__init__(parent) self.setupUi(self) + self.model = None self.fill_list() self.text_list.clicked.connect(self.slot_show_text) @@ -199,7 +205,7 @@ class TextSorterDialog(QtGui.QWidget, Ui_TextSorterDialog): self.text_list.clicked.connect(self.slot_toggle_buttons) self.move_up_button.setEnabled(False) self.move_down_button.setEnabled(False) - self.model = None + def slot_toggle_buttons(self, index): row = index.row() @@ -260,61 +266,6 @@ class TextSorterDialog(QtGui.QWidget, Ui_TextSorterDialog): self.text_list.clicked.emit(index) self.parent().parent().db_dirty = True -class FadeAnimation(QtCore.QObject): - animation_started = QtCore.pyqtSignal() - animation_finished = QtCore.pyqtSignal() - animation_stopped = QtCore.pyqtSignal() - - def __init__(self, live_text, fade_steps=6, parent=None): - super(FadeAnimation, self).__init__(parent) - self.live_text = live_text - self.fade_steps = fade_steps - self.current_alpha = 255 - self.timer = None - - def start_animation(self): - print "start_animation" - self.animation_started.emit() - - if self.current_alpha == 255: - self.fade_delta = 255 / self.fade_steps - else: - self.fade_delta = -255 / self.fade_steps - self.timer = QtCore.QTimer(self) - self.timer.timeout.connect(self.slot_animate) - self.timer.start(100) - - def slot_animate(self): - print "slot_animate" - print "current_alpha", self.current_alpha - if self.fade_delta > 0: - if self.current_alpha > 0: - self.live_text.setStyleSheet("color:%d, %d, %d;" % (self.current_alpha, self.current_alpha, self.current_alpha)) - self.current_alpha -= self.fade_delta - else: - self.live_text.setStyleSheet("color:black;") - self.current_alpha = 0 - self.timer.stop() - self.timer.timeout.disconnect(self.slot_animate) - self.timer.deleteLater() - self.timer = None - self.animation_finished.emit() - print "animation_finished" - else: - if self.current_alpha < 255: - self.live_text.setStyleSheet("color:%d,%d, %d;" % (self.current_alpha, self.current_alpha, self.current_alpha)) - self.current_alpha -= self.fade_delta - else: - self.live_text.setStyleSheet("color:white") - self.current_alpha = 255 - self.timer.stop() - self.timer.timeout.disconnect(self.slot_animate) - self.timer.deleteLater() - self.timer = None - self.animation_finished.emit() - print "animation_finished" - - class TextAnimation(QtCore.QObject): animation_started = QtCore.pyqtSignal() animation_finished = QtCore.pyqtSignal() @@ -442,8 +393,6 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.setupUi(self) - self.fade_animation = FadeAnimation(self.live_text, 6, self) - self.font = QtGui.QFont("monospace", self.default_size) self.font.setStyleHint(QtGui.QFont.TypeWriter) @@ -464,14 +413,12 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.typer_animation_action = None self.text_editor_action = None - #self.preview_text.document().setDefaultFont(self.font) self.preview_text.setFont(self.font) self.preview_text.setRichTextSupport(KRichTextWidget.RichTextSupport(0xffffffff)) self.preview_editor_collection = KActionCollection(self) self.preview_text.createActions(self.preview_editor_collection) self.live_text.setRichTextSupport(KRichTextWidget.RichTextSupport(0xffffffff)) - #self.live_text.document().setDefaultFont(self.font) self.live_text.setFont(self.font) self.live_editor_collection = KActionCollection(self) self.live_text.createActions(self.live_editor_collection) @@ -479,34 +426,18 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.create_toolbar() self.slot_load() - app.focusChanged.connect(self.focusChanged) self.start_streaming() - self.get_live_coords() self.show() - def get_live_coords(self): - public_rect = self.live_text.geometry() - global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight())) - x = global_rect.x() - y = global_rect.y() - self.statusBar().showMessage("live text editor dimensions: x=%r, y=%r, width=%r, height=%r" % (x, y, global_rect.width(), global_rect.height())) - - def get_preview_coords(self): - public_rect = self.preview_text.geometry() - global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight())) - return global_rect.x(), global_rect.y() - def filter_editor_actions(self): - disabled_action_names = [ "action_to_plain_text", "format_painter", "direction_ltr", "direction_rtl", "format_font_family", - #"format_font_size", "format_text_background_color", "format_list_style", "format_list_indent_more", @@ -516,7 +447,6 @@ class MainWindow(KMainWindow, Ui_MainWindow): "format_text_strikeout", "format_text_italic", "format_align_right", - #"format_align_justify", "manage_link", "format_text_subscript", "format_text_superscript", @@ -525,7 +455,6 @@ class MainWindow(KMainWindow, Ui_MainWindow): for action in self.live_editor_collection.actions(): text = str(action.objectName()) - #print "text", text if text in disabled_action_names: action.setVisible(False) @@ -554,6 +483,7 @@ class MainWindow(KMainWindow, Ui_MainWindow): def create_toolbar(self): self.toolbar = KToolBar(self, True, True) + self.toolbar.setIconDimensions(16) self.toolbar.setAllowedAreas(QtCore.Qt.BottomToolBarArea) self.toolbar.setMovable(False) self.toolbar.setFloatable(False) @@ -565,32 +495,31 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.action_collection.addAssociatedWidget(self.toolbar) self.clear_live_action = self.action_collection.addAction("clear_live_action") - icon = QtGui.QIcon.fromTheme(_fromUtf8("edit-clear")) + icon = QtGui.QIcon(":texter/images/edit-clear.png") self.clear_live_action.setIcon(icon) self.clear_live_action.setIconText("clear live") self.clear_live_action.setShortcut(KShortcut(QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_Q)), KAction.ShortcutTypes(KAction.ActiveShortcut | KAction.DefaultShortcut)) self.save_live_action = self.action_collection.addAction("save_live_action") - icon = QtGui.QIcon.fromTheme(_fromUtf8("document-new")) + icon = QtGui.QIcon(":texter/images/document-new.png") self.save_live_action.setIcon(icon) self.save_live_action.setIconText("save live") self.save_live_action.setShortcut(KShortcut(QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_W)), KAction.ShortcutTypes(KAction.ActiveShortcut | KAction.DefaultShortcut)) self.clear_preview_action = self.action_collection.addAction("clear_preview_action") - icon = QtGui.QIcon.fromTheme(_fromUtf8("edit-clear")) + icon = QtGui.QIcon(":texter/images/edit-clear.png") self.clear_preview_action.setIcon(icon) self.clear_preview_action.setIconText("clear preview") - #self.clear_preview_action.setObjectName("clear_preview") self.clear_preview_action.setShortcut(KShortcut(QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_A)), KAction.ShortcutTypes(KAction.ActiveShortcut | KAction.DefaultShortcut)) self.save_preview_action = self.action_collection.addAction("save_preview_action") - icon = QtGui.QIcon.fromTheme(_fromUtf8("document-new")) + icon = QtGui.QIcon(":texter/images/document-new.png") self.save_preview_action.setIcon(icon) self.save_preview_action.setIconText("save preview") self.save_preview_action.setShortcut(KShortcut(QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_S)), KAction.ShortcutTypes(KAction.ActiveShortcut | KAction.DefaultShortcut)) self.publish_action = self.action_collection.addAction("publish_action") - icon = QtGui.QIcon.fromTheme(_fromUtf8("edit-copy")) + icon = QtGui.QIcon(":texter/images/edit-copy.png") self.publish_action.setIcon(icon) self.publish_action.setIconText("publish") self.publish_action.setShortcutConfigurable(True) @@ -600,14 +529,14 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.auto_publish_action = KToggleAction(self.action_collection) self.action_collection.addAction("auto publish", self.auto_publish_action) - icon = QtGui.QIcon.fromTheme(_fromUtf8("view-refresh")) + icon = QtGui.QIcon(":texter/images/view-refresh.png") self.auto_publish_action.setIcon(icon) self.auto_publish_action.setObjectName("auto_publish_action") self.auto_publish_action.setIconText("auto publish") self.auto_publish_action.setShortcut(KShortcut(QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_P)), KAction.ShortcutTypes(KAction.ActiveShortcut | KAction.DefaultShortcut)) self.typer_animation_action = KToggleAction(self.action_collection) - icon = QtGui.QIcon.fromTheme(_fromUtf8("media-playback-stop")) + icon = QtGui.QIcon(":texter/images/media-playback-stop.png") self.typer_animation_action.setIcon(icon) self.typer_animation_action.setIconText("animate") self.typer_animation_action.setObjectName("typer_animation_action") @@ -615,7 +544,7 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.action_collection.addAction("typer_animation_action", self.typer_animation_action) self.text_editor_action = self.action_collection.addAction("text_editor_action") - icon = QtGui.QIcon.fromTheme(_fromUtf8("document-open-data")) + icon = QtGui.QIcon(":texter/images/document-open-data.png") self.text_editor_action.setIcon(icon) self.text_editor_action.setIconText("edit") self.text_editor_action.setShortcut(KShortcut(QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_O)), KAction.ShortcutTypes(KAction.ActiveShortcut | KAction.DefaultShortcut)) @@ -623,13 +552,13 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.toolbar.insertSeparator(self.text_editor_action) self.save_action = self.action_collection.addAction("save_action") - icon = QtGui.QIcon.fromTheme(_fromUtf8("document-save")) + icon = QtGui.QIcon(":texter/images/document-save.png") self.save_action.setIcon(icon) self.save_action.setIconText("save") self.save_action.setShortcut(KShortcut(QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_S)), KAction.ShortcutTypes(KAction.ActiveShortcut | KAction.DefaultShortcut)) self.streaming_action = KToggleAction(self.action_collection) - icon = QtGui.QIcon.fromTheme(_fromUtf8("media-record")) + icon = QtGui.QIcon(":texter/images/media-record.png") self.streaming_action.setIcon(icon) self.streaming_action.setIconText("stream") self.streaming_action.setObjectName("stream") @@ -639,28 +568,22 @@ class MainWindow(KMainWindow, Ui_MainWindow): spacer = KToolBarSpacerAction(self.action_collection) self.action_collection.addAction("1_spacer", spacer) - #self.fade_action = self.action_collection.addAction("fade_action") - ##icon = QtGui.QIcon.fromTheme(_fromUtf8("go-previous-view-page")) - ##self.fade_action.setIcon(icon) - #self.fade_action.setIconText("fade") - #self.fade_action.setShortcut(KShortcut(QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_F)), KAction.ShortcutTypes(KAction.ActiveShortcut | KAction.DefaultShortcut)) - self.previous_action = self.action_collection.addAction("previous_action") - icon = QtGui.QIcon.fromTheme(_fromUtf8("go-previous-view-page")) + icon = QtGui.QIcon(":texter/images/go-previous-view-page.png") self.previous_action.setIcon(icon) self.previous_action.setIconText("previous") self.previous_action.setShortcut(KShortcut(QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_Left)), KAction.ShortcutTypes(KAction.ActiveShortcut | KAction.DefaultShortcut)) self.text_combo = KSelectAction(self.action_collection) self.text_combo.setEditable(False) - icon = QtGui.QIcon.fromTheme(_fromUtf8("document-open-recent")) + icon = QtGui.QIcon(":texter/images/document-open-recent.png") self.text_combo.setIcon(icon) self.text_combo.setIconText("saved texts") self.text_combo.setObjectName("text_combo") self.action_collection.addAction("saved texts", self.text_combo) self.next_action = self.action_collection.addAction("next_action") - icon = QtGui.QIcon.fromTheme(_fromUtf8("go-next-view-page")) + icon = QtGui.QIcon(":texter/images/go-next-view-page.png") self.next_action.setIcon(icon) self.next_action.setIconText("next") self.next_action.setShortcut(KShortcut(QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_Right)), KAction.ShortcutTypes(KAction.ActiveShortcut | KAction.DefaultShortcut)) @@ -682,7 +605,6 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.preview_size_action.triggered[QtGui.QAction].connect(self.slot_preview_font_size) self.live_size_action.triggered[QtGui.QAction].connect(self.slot_live_font_size) - #self.fade_action.triggered.connect(self.slot_fade) self.next_action.triggered.connect(self.slot_next_item) self.previous_action.triggered.connect(self.slot_previous_item) self.streaming_action.setChecked(True) @@ -889,12 +811,11 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.dialog = None self.dialog = KDialog(self) - self.dialog_widget = TextSorterDialog(self.dialog) + self.dialog.setButtons(KDialog.Close) + self.dialog_widget = EditDialog(self.dialog) self.dialog.setMainWidget(self.dialog_widget) - pos_x, pos_y = self.get_preview_coords() - self.dialog.move(pos_x, 0) - self.dialog.okClicked.connect(self.fill_combo_box) self.dialog.exec_() + self.fill_combo_box() def slot_load(self): path = os.path.expanduser("~/.texter") @@ -908,7 +829,7 @@ class MainWindow(KMainWindow, Ui_MainWindow): try: self.model.text_db = [list(i) for i in cPickle.load(db_file)] except ValueError, error: - print error + logger.exception(error) self.fill_combo_box() self.text_combo.setCurrentItem(0) @@ -916,7 +837,7 @@ class MainWindow(KMainWindow, Ui_MainWindow): def main(): - arg_parser = ArgParser("dump_grabber") + arg_parser = ArgParser("texter") arg_parser.add_global_group() client_group = arg_parser.add_client_group() arg_parser.add_argument(client_group, '-x', "--http_host", default="::", diff --git a/texter/texter/text_model.py b/texter/texter/text_model.py index b3a9262..558ef45 100644 --- a/texter/texter/text_model.py +++ b/texter/texter/text_model.py @@ -19,8 +19,6 @@ class TextModel(QtCore.QAbstractTableModel): return 2 def data(self, index, role): - if role not in (1,3,4,5,6,7,8,9,10,13): - print "role", role if not index.isValid() or \ not 0 <= index.row() < self.rowCount(): return QVariant() diff --git a/texter/texter/text_sorter.ui b/texter/texter/text_sorter.ui deleted file mode 100644 index e5c8389..0000000 --- a/texter/texter/text_sorter.ui +++ /dev/null @@ -1,157 +0,0 @@ - - - text_sorter_dialog - - - - 0 - 0 - - - - Dialog - - - - - - - - - 0 - 576 - - - - - 16777215 - 576 - - - - - - - - - - - Remove - - - - - - - - - - - - - 2 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - KArrowButton - QPushButton -
karrowbutton.h
-
- - KRichTextEdit - KTextEdit -
krichtextedit.h
-
- - KButtonGroup - QGroupBox -
kbuttongroup.h
- 1 -
- - KPushButton - QPushButton -
kpushbutton.h
-
- - KTextEdit - QTextEdit -
ktextedit.h
-
- - KRichTextWidget - KRichTextEdit -
krichtextwidget.h
-
-
- - - - buttonBox - accepted() - text_sorter_dialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - text_sorter_dialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - -
diff --git a/texter/texter/texter.qrc b/texter/texter/texter.qrc index b4b4a29..6416308 100644 --- a/texter/texter/texter.qrc +++ b/texter/texter/texter.qrc @@ -1,5 +1,16 @@ + images/document-new.png + images/document-open-data.png + images/document-open-recent.png + images/document-save.png + images/edit-clear.png + images/edit-copy.png + images/go-next-view-page.png + images/go-previous-view-page.png + images/media-playback-stop.png + images/media-record.png + images/view-refresh.png icon.png diff --git a/texter/texter/texter.ui b/texter/texter/texter.ui index 335ee8d..923809b 100644 --- a/texter/texter/texter.ui +++ b/texter/texter/texter.ui @@ -6,67 +6,13 @@ 0 0 - 1207 - 634 + 1475 + 592 - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - @@ -76,70 +22,7 @@ - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - 0 - 0 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - 0 @@ -150,60 +33,6 @@ - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - @@ -213,70 +42,7 @@ - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - 0 - 0 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - 0 @@ -287,338 +53,67 @@ - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 + 169 + 167 + 167 - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 + 244 + 244 + 244 + + + Helvetica + 7 + + 4.48 Texter + + + :/texter/icon.png:/texter/icon.png + - - - - - - &Title - - - item_title - - - - - - - - 100 - 0 - - - - - - - - P&osition - - - item_position_input - - - - - - - - 50 - 16777215 - - - - - - - - S&election - - - edit_item_selection - - - - - - - - 100 - 0 - - - - - - - - &Add / Change - - - - - - - &Remove - - - - - - - Sa&ve - - - - - - - &Publish - - - false - - - - - - - Clear Live - - - - - - - Clear Preview - - - - - - - Previous - - - - - - - Next - - - - - - - - - - A&uto Publish - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - 768 - 576 + 775 + 582 - 768 - 576 + 775 + 582 - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - 0 0 - 255 + 0 @@ -627,36 +122,36 @@ 0 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 255 - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - 0 0 - 255 + 0 @@ -665,36 +160,36 @@ 0 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 255 - - - - 128 - 125 - 123 - - - - - - - 128 - 125 - 123 - - - 0 0 - 255 + 0 @@ -703,6 +198,24 @@ 0 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 255 @@ -710,14 +223,14 @@ - - - Monospace - 22 - - - true + false + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff true @@ -725,49 +238,40 @@ Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + KRichTextWidget::SupportAlignment|KRichTextWidget::SupportFontFamily|KRichTextWidget::SupportFontSize|KRichTextWidget::SupportTextForegroundColor + + + + 10 + 0 + + - 400 - 576 + 300 + 582 - 768 - 576 + 775 + 582 - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - 0 0 - 255 + 0 @@ -776,36 +280,18 @@ 0 0 - 255 + 0 - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - 0 0 - 255 + 0 @@ -814,36 +300,18 @@ 0 0 - 255 + 0 - - - - 128 - 125 - 123 - - - - - - - 128 - 125 - 123 - - - 0 0 - 255 + 0 @@ -852,24 +320,18 @@ 0 0 - 255 + 0 - - - Monospace - 22 - - - - Qt::ActionsContextMenu + + preview text - true + false QFrame::StyledPanel @@ -880,13 +342,15 @@ Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + KRichTextWidget::SupportAlignment|KRichTextWidget::SupportChangeListStyle|KRichTextWidget::SupportFontFamily|KRichTextWidget::SupportFontSize|KRichTextWidget::SupportTextForegroundColor + - @@ -904,17 +368,13 @@ KRichTextEdit
krichtextwidget.h
- - KIntNumInput - QWidget -
knuminput.h
-
- item_title - item_position_input - remove_item_button + live_text + preview_text - + + +
diff --git a/texter/texter/texter2.ui b/texter/texter/texter2.ui deleted file mode 100644 index c4e8da1..0000000 --- a/texter/texter/texter2.ui +++ /dev/null @@ -1,395 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1554 - 617 - - - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - - - 169 - 167 - 167 - - - - - - - 244 - 244 - 244 - - - - - - - - 4.48 Texter - - - - :/texter/icon.png:/texter/icon.png - - - - - - - - - - 768 - 576 - - - - - 768 - 576 - - - - - Monospace - 22 - - - - BlankCursor - - - - - - - - - - - - false - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - - clear live text [F1] - - - - - - - - F1 - - - - - - - save live text [F2] - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - starts/stops live textfield streaming [F9] - - - - - - true - - - - - - - - - - - - - - 400 - 576 - - - - - 768 - 576 - - - - - Monospace - 22 - - - - Qt::ActionsContextMenu - - - preview text - - - false - - - QFrame::StyledPanel - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - - clear preview text [F4] - - - - - - - - - - - - - - F2 - - - - - - - - - - - - - - - - ArrowCursor - - - go live with text [F5] - - - - - - - - F4 - - - - - - - Qt::Vertical - - - - - - - load previous text [F6] - - - - - - - - - - - - load next text [F7] - - - - - - - - - - - - - 0 - 0 - - - - true - - - - - - - edit sorting of saved texts [F10] - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - KRichTextEdit - KTextEdit -
krichtextedit.h
-
- - KComboBox - QComboBox -
kcombobox.h
-
- - KPushButton - QPushButton -
kpushbutton.h
-
- - KTextEdit - QTextEdit -
ktextedit.h
-
- - KRichTextWidget - KRichTextEdit -
krichtextwidget.h
-
-
- - live_text - preview_text - clear_preview_button - clear_live_button - - - - - -
diff --git a/texter/texter/texter3.ui b/texter/texter/texter3.ui deleted file mode 100644 index 90aeb6e..0000000 --- a/texter/texter/texter3.ui +++ /dev/null @@ -1,403 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1475 - 651 - - - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - - - 169 - 167 - 167 - - - - - - - 244 - 244 - 244 - - - - - - - - 4.48 Texter - - - - :/texter/icon.png:/texter/icon.png - - - - - - - - - - 775 - 582 - - - - - 775 - 582 - - - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - - false - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - KRichTextWidget::SupportAlignment|KRichTextWidget::SupportFontFamily|KRichTextWidget::SupportFontSize|KRichTextWidget::SupportTextForegroundColor - - - - - - - - 10 - 0 - - - - - 300 - 582 - - - - - 775 - 582 - - - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - - preview text - - - false - - - QFrame::StyledPanel - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - KRichTextWidget::SupportAlignment|KRichTextWidget::SupportChangeListStyle|KRichTextWidget::SupportFontFamily|KRichTextWidget::SupportFontSize|KRichTextWidget::SupportTextForegroundColor - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - KRichTextEdit - KTextEdit -
krichtextedit.h
-
- - KTextEdit - QTextEdit -
ktextedit.h
-
- - KRichTextWidget - KRichTextEdit -
krichtextwidget.h
-
-
- - live_text - preview_text - - - - - -
diff --git a/texter/texter/texter_rc.py b/texter/texter/texter_rc.py index 6f17bd2..be779fe 100644 --- a/texter/texter/texter_rc.py +++ b/texter/texter/texter_rc.py @@ -2,7 +2,7 @@ # Resource object code # -# Created: Sa. Apr 12 08:49:54 2014 +# Created: So. Mai 11 12:37:13 2014 # by: The Resource Compiler for PyQt (Qt v4.8.5) # # WARNING! All changes made in this file will be lost! @@ -47,6 +47,482 @@ qt_resource_data = "\ \x38\x8f\xeb\x38\x43\x35\x89\xdd\x94\xa5\xf7\x2f\xa8\x01\x6a\x80\ \x1a\xa0\x06\xf8\xdf\xf2\x1b\xf1\xb2\x57\x16\x4f\x60\xf0\x28\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\x7f\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x03\x00\x00\x00\x28\x2d\x0f\x53\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x09\x70\x48\x59\x73\x00\x00\x06\xec\x00\x00\x06\xec\x01\x1e\x75\ +\x38\x35\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd8\x07\x03\x0a\x15\ +\x1c\xbc\x0f\x78\x91\x00\x00\x01\x11\x50\x4c\x54\x45\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x32\x71\x2d\x8f\x90\x91\ +\x93\x93\x94\x8c\x8e\x8f\x90\x90\x91\x18\x48\x1d\x18\x49\x28\x1b\ +\x57\x19\x1d\x56\x17\x21\x62\x1e\x27\x70\x28\x32\x82\x34\x3d\x8e\ +\x3c\x3e\x7e\x18\x54\x9d\x4c\x55\x87\x5d\x55\x9e\x4f\x5b\x9d\x56\ +\x63\x9f\x38\x64\xa7\x53\x74\xa4\x77\x75\xac\x0c\x77\xb2\x50\x7c\ +\xb3\x78\x85\xbe\x20\x8b\xa9\x92\x8e\xc4\x23\x8f\x94\x92\x91\xc8\ +\x24\x99\xd2\x0b\x9b\x9d\x9e\x9e\x9f\xa0\x9f\x9f\xa0\x9f\xad\xa3\ +\xa0\xd7\x0c\xa2\xa3\xa3\xa6\xa7\xa8\xa7\xa7\xa8\xa8\xa8\xa8\xa8\ +\xd0\x6a\xaa\xd0\x72\xb9\xb9\xb9\xba\xba\xba\xbf\xde\x9a\xc5\xc5\ +\xc5\xc5\xc6\xc6\xc8\xca\xcb\xd2\xd2\xd2\xd2\xda\xd4\xd2\xe2\xd2\ +\xd8\xe9\xd6\xd9\xd9\xd9\xdb\xdb\xdb\xdf\xed\xde\xe3\xe3\xe3\xe9\ +\xef\xe9\xea\xea\xea\xee\xee\xee\xee\xef\xef\xef\xef\xef\xf0\xf0\ +\xf0\xf2\xf7\xf1\xf4\xf5\xf5\xf5\xf5\xf5\xf5\xf6\xf6\xf6\xf6\xf6\ +\xf7\xf7\xf7\xf8\xf8\xf8\xf8\xf9\xf9\xf9\xf9\xf9\xfa\xfa\xfa\xfb\ +\xfb\xfb\xfc\xfc\xfc\xfd\xfd\xfd\xfe\xfe\xfe\xff\xff\xff\x79\x1f\ +\xc0\x25\x00\x00\x00\x14\x74\x52\x4e\x53\x00\x10\x13\x15\x1b\x1c\ +\x24\x25\x26\x3a\x3b\x44\x4e\x4f\x7e\x8b\xdb\xdb\xdc\xdc\x37\x8a\ +\x8c\x11\x00\x00\x00\x01\x62\x4b\x47\x44\x5a\x03\xbb\xa5\xa2\x00\ +\x00\x00\xc7\x49\x44\x41\x54\x78\xda\x3d\x8c\x7b\x57\x01\x51\x14\ +\x47\xaf\x47\x1e\xdd\x48\x49\x09\x21\xa2\xbc\x9a\xd0\x28\xc9\x23\ +\x0a\x0d\xaa\x99\xdc\xb9\xe7\xce\xf7\xff\x20\x9d\xb3\xe6\xae\x7e\ +\x7f\x9d\xbd\xd7\x5e\x87\x05\xe3\xff\x0b\x30\x5a\xc2\xd3\x7b\x7b\ +\xe7\x5a\xec\x7e\x9c\xbd\x10\xa2\x74\x75\x7c\x48\x55\x42\xd9\xbf\ +\x42\xb8\xae\x9b\x7f\xa9\xd4\xb7\x1e\x47\xb1\x47\x94\x52\xe6\x9e\ +\x06\xb5\xf2\x0c\x85\x27\x10\x01\xe0\xf2\xb6\xd5\xb8\xae\x92\x20\ +\x56\x0a\xb2\xed\x6e\xa1\xb8\xe4\xec\x08\x05\x28\x69\x5b\x17\x37\ +\xe7\x77\x12\x48\x20\xc3\xca\x34\xce\x32\x0f\x00\x0a\x85\x42\x61\ +\x9b\x0a\xa6\xcd\x85\xb5\xfe\xf6\x0b\xd8\x18\xe0\x74\x46\xa6\xd1\ +\x1f\x6b\xf1\xf9\xe8\xdc\x4f\xb0\x72\xd2\x07\xfe\x8f\xaf\xd7\xde\ +\x9c\xaa\xd3\x18\x63\x21\xce\x79\x72\xf8\xfc\x41\x55\x2a\xca\xfc\ +\xe9\xea\x24\xa2\x99\x2a\x5a\x98\xee\x3f\xcd\x34\x32\x2a\x58\xbf\ +\xee\x5f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\x6a\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x03\x00\x00\x00\x28\x2d\x0f\x53\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x09\x70\x48\x59\x73\x00\x00\x06\xec\x00\x00\x06\xec\x01\x1e\x75\ +\x38\x35\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd8\x07\x03\x0f\x2d\ +\x32\x71\x8d\x0b\x4e\x00\x00\x01\x0b\x50\x4c\x54\x45\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8f\x90\x91\x93\x93\x94\ +\x8c\x8e\x8f\x90\x90\x91\x0b\x47\x16\x16\x47\x1d\x18\x5c\x1c\x20\ +\x59\x1a\x23\x6e\x25\x28\x56\x32\x34\x83\x35\x3f\x8f\x3c\x40\x7e\ +\x17\x54\x98\x51\x54\x9d\x4c\x55\x95\x2a\x5a\x8a\x61\x5d\xa2\x58\ +\x61\xa5\x51\x73\xaf\x4e\x74\xa9\x70\x75\x91\x7c\x84\xb7\x0d\x84\ +\xbd\x23\x87\xaf\x89\x89\xc2\x20\x90\xc7\x24\x9d\xd4\x0d\x9f\x9f\ +\xa0\xa1\xd9\x0c\xa6\xa7\xa8\xa6\xc6\xa4\xa7\xa7\xa8\xa7\xd0\x6a\ +\xa8\xa8\xa8\xab\xd1\x71\xaf\xaf\xaf\xb0\xb0\xb0\xbf\xde\x99\xc0\ +\xc0\xc0\xc1\xc1\xc1\xc5\xc6\xc6\xc8\xca\xcb\xca\xd6\xcd\xcc\xe2\ +\xca\xd3\xdc\xd6\xd6\xe4\xd7\xe2\xee\xe1\xe5\xe5\xe5\xea\xea\xea\ +\xea\xef\xea\xed\xf2\xed\xee\xee\xee\xee\xef\xef\xee\xf2\xef\xef\ +\xef\xef\xf0\xf0\xf0\xf4\xf5\xf5\xf5\xf5\xf5\xf5\xf6\xf5\xf5\xf6\ +\xf6\xf6\xf6\xf6\xf6\xf7\xf6\xf7\xf7\xf7\xf8\xf8\xf8\xf8\xf9\xf8\ +\xf9\xf9\xf9\xfa\xfa\xfa\xfb\xfb\xfb\xfc\xfc\xfc\xfd\xfd\xfd\xfd\ +\xfe\xfd\xfe\xfe\xfe\xff\xff\xff\xd9\x1d\x54\xe2\x00\x00\x00\x13\ +\x74\x52\x4e\x53\x00\x10\x13\x15\x1b\x1c\x24\x25\x26\x3a\x44\x4e\ +\x4f\x6f\x7e\xdb\xdb\xdc\xdc\xc6\x90\x16\x3a\x00\x00\x00\x01\x62\ +\x4b\x47\x44\x58\xed\xb5\xc4\x8e\x00\x00\x00\xb9\x49\x44\x41\x54\ +\x18\x19\x05\xc1\x5d\x2e\x03\x51\x18\x00\xd0\xf3\xdd\x7e\x4a\x91\ +\x8c\x90\x88\x48\x08\x89\x58\x8d\xd5\x76\x05\x7e\x36\x60\x05\xbc\ +\xe0\x41\x24\xd5\xd0\x76\x66\x6a\xee\x38\x27\xcb\x2e\x80\x76\x44\ +\xee\xdf\xc1\xc7\x70\xc1\x7c\x8d\xe4\x3b\x7f\x16\x8b\xc9\x56\x4c\ +\x66\xb4\x69\x9c\xfe\xbe\xd5\xb3\xf7\xab\xe2\x1a\xf3\x64\xf1\x3a\ +\x6d\x86\x71\x35\x0d\x90\xe2\xb3\x64\x37\xd6\x12\x81\x90\x9c\xbf\ +\xac\x9a\x49\x15\x01\x72\x74\x7c\xf2\xfc\xd5\xd4\x21\x82\x52\x25\ +\x11\xcd\xe9\x4b\x4c\xa3\xa8\xb5\x48\x23\x71\x79\xb0\xd9\x29\x43\ +\xdf\x0e\x7b\x52\x40\x73\x54\xea\xd7\xb2\x2f\x87\x12\x8c\xe8\x96\ +\xb7\xb5\x7f\xda\x26\x08\x6a\xd7\xd7\xfe\x71\xb3\xcd\x76\x4e\xde\ +\x60\xc8\xfe\xbe\xed\xe4\xb0\xe6\x00\x65\x67\xf6\xb0\xe9\x49\x68\ +\xe7\xa0\xff\xc3\x3f\x9e\x71\x4d\xf4\x47\x46\x2f\xfd\x00\x00\x00\ +\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\x43\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\ +\x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\xc0\x49\x44\ +\x41\x54\x78\xda\x8d\x8f\xbd\x6b\x53\x51\x18\x87\x9f\x73\x72\xc1\ +\xb6\x41\x90\x82\x52\x2a\xc5\x39\xdd\x8c\x93\x75\xab\x83\x9b\xbb\ +\x53\xbb\xe4\x3f\x68\x07\x11\x8a\x25\xd4\x82\x43\x87\xa2\x20\x0e\ +\x15\x07\xe9\x6a\x16\xc5\x3d\xb5\x88\xa1\x1f\x42\xd3\x4d\x25\xe8\ +\xcd\x87\x83\x12\x92\x9b\x8f\x9b\x9c\xd7\xe6\xe0\xe1\x70\x89\x5f\ +\x3f\xf8\x71\xce\x19\x9e\xe7\x7d\x8f\x02\x2e\x00\xf3\xfc\x2b\xab\ +\xdc\xce\x5c\xcb\x2c\x65\x2f\x67\xa7\x0f\xab\x87\xf5\x72\xbb\xfc\ +\x40\x96\xe5\x49\x30\x82\x1b\x8d\xc6\x9e\x11\x41\x6b\x6d\xab\x94\ +\x4a\xf4\xd9\xc7\x1d\x76\x7f\xec\xb2\x78\x69\x91\xcd\xf9\x4d\xee\ +\x95\xef\xce\xa5\xbf\xa5\x1f\xab\x1d\x25\x1a\xc0\x08\x7f\xcd\xf6\ +\xe9\x36\xd5\x4e\x95\xfd\x4f\x05\x00\x3e\x57\x0b\x98\x41\x4d\x63\ +\x58\x0d\x00\x52\x29\xeb\xb1\xd3\xdc\x06\xda\x6f\x40\xed\x7b\x8d\ +\xe8\x4e\x84\xcb\x8b\x9b\x65\x00\xd4\x23\x35\x1b\x00\x16\x42\x04\ +\xf5\xeb\x0b\x16\x76\xe7\x59\x67\xae\xcc\x10\x3c\x57\x5c\x05\xde\ +\x2f\x09\xb9\x82\xe2\x40\x80\x8b\x84\xda\x4d\x76\xb0\x6b\xca\xdf\ +\x59\xc9\xac\x20\xe7\x35\x61\x0a\x9b\x0f\xc0\xb1\xc1\x30\xc5\x43\ +\xbf\x01\x78\x81\xff\x8a\x6d\x6e\x2e\x07\x1a\xb6\xbe\x6e\x91\x7e\ +\x99\x26\x0a\xf8\xc2\x04\x79\xb9\x25\x4f\x15\xb0\xd0\x6c\x36\xf7\ +\xbc\xc0\xd7\x6d\xe7\x4e\x97\x20\x08\x6e\x88\xc8\x5b\x7b\x07\x50\ +\x7e\x03\x57\x0f\x26\xce\x64\xbc\x00\xdc\xba\x6e\xb2\x83\xfe\x4f\ +\x40\x12\x76\x60\x42\x60\x8c\xb1\x6d\xb7\x23\x01\x62\x2f\x18\x03\ +\xc6\xe1\x4a\xa5\x42\x18\x86\x4c\x4c\x4e\xf2\xea\xf5\x9b\x77\xc0\ +\x11\xf8\x2c\x44\x51\x24\xdd\x6e\x57\xfa\xfd\xbe\xc4\x71\x2c\x83\ +\xc1\x40\x86\xc3\xa1\x18\x63\xec\xbb\x58\x2c\xda\xf7\xfd\x8d\x8d\ +\x13\x60\x4a\x44\x70\xc5\x0a\x3a\x9d\x3f\x0a\x5a\xad\x96\x94\x4a\ +\x25\x59\xcb\xe7\x47\xf0\xb4\x87\xbd\xe0\xfa\x99\xc0\xf4\x7a\xbd\ +\x11\x9c\x10\x8c\x52\xaf\x37\xcc\xda\xfa\xfa\xbe\x9f\x9c\xac\x02\ +\xce\x01\x59\x40\xf1\xfb\xc4\xc0\x91\x88\xc4\x30\x9e\x9f\xaf\xc9\ +\x06\x51\x54\x9d\xd3\x94\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ +\x60\x82\ +\x00\x00\x02\xdf\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x03\x00\x00\x00\x28\x2d\x0f\x53\ +\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\x01\ +\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\ +\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\ +\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x4d\x50\x4c\x54\ +\x45\xff\xff\xff\x59\x59\x59\x5c\x5c\x5c\x80\x80\x80\x83\x83\x83\ +\x56\x56\x56\x63\x63\x63\x79\x79\x79\x86\x86\x86\x57\x57\x57\x5e\ +\x5e\x5e\x7a\x7a\x7a\x81\x81\x81\x64\x64\x64\x74\x74\x74\x55\x55\ +\x55\x64\x64\x64\x74\x74\x74\x84\x84\x84\x53\x53\x53\x66\x66\x66\ +\x6f\x6f\x6f\x82\x82\x82\x5b\x5b\x5b\x7f\x7f\x7f\x52\x52\x52\x67\ +\x67\x67\x6b\x6b\x6b\x80\x80\x80\x55\x55\x55\x5f\x5f\x5f\x74\x74\ +\x74\x7d\x7d\x7d\x52\x52\x52\x54\x54\x54\x58\x58\x58\x5d\x5d\x5d\ +\x5f\x5f\x5f\x61\x61\x61\x66\x66\x66\x6a\x6a\x6a\x6b\x6b\x6b\x6d\ +\x6d\x6d\x6f\x6f\x6f\x71\x71\x71\x74\x74\x74\x75\x75\x75\x76\x76\ +\x76\x77\x77\x77\x79\x79\x79\x7a\x7a\x7a\x80\x80\x80\x81\x81\x81\ +\x88\x88\x88\x8f\x8f\x8f\x90\x90\x90\x9c\x9c\x9c\xb8\xb8\xb8\xba\ +\xba\xba\xbe\xbe\xbe\xce\xce\xce\xd0\xd0\xd0\xd3\xd3\xd3\xd4\xd4\ +\xd4\xd5\xd5\xd5\xd6\x04\x08\xd6\x35\x39\xd9\x06\x09\xd9\xd9\xd9\ +\xda\x1f\x22\xda\x45\x48\xdb\xdb\xdb\xdc\x55\x58\xdd\xdd\xdd\xde\ +\x37\x3a\xdf\x2d\x30\xdf\x39\x3c\xdf\x3d\x40\xdf\x60\x63\xe1\x08\ +\x0a\xe1\x39\x3b\xe2\xe2\xe2\xe3\x1c\x1e\xe7\x0b\x0c\xe7\x39\x3b\ +\xe8\xe8\xe8\xe9\xe9\xe9\xea\xea\xea\xeb\x2f\x30\xeb\x9e\xa0\xec\ +\x9e\xa0\xec\xec\xec\xed\xa3\xa5\xed\xa8\xaa\xed\xed\xed\xef\xef\ +\xef\xf0\xa0\xa1\xf0\xb8\xb9\xf1\xbd\xbf\xf1\xf1\xf1\xf2\xf2\xf2\ +\xf3\xf3\xf3\xf5\x0f\x0f\xf6\xf6\xf6\xfa\xfa\xfa\xfb\xfb\xfb\xfc\ +\xfa\xfa\xfc\xfc\xfc\xfd\xfd\xfd\xfe\xfa\xfa\xff\xff\xff\x7f\x3d\ +\xcd\x14\x00\x00\x00\x21\x74\x52\x4e\x53\x00\x01\x01\x01\x01\x26\ +\x26\x26\x26\x71\x71\x71\x71\x80\x80\x81\x81\x81\x81\xb5\xb5\xb5\ +\xb5\xb6\xb6\xe7\xe7\xe7\xe7\xf6\xf6\xf6\xf6\x96\x0a\xc3\x45\x00\ +\x00\x00\xd7\x49\x44\x41\x54\x18\x19\x05\xc1\xd1\x4e\x83\x30\x18\ +\x06\xd0\xaf\xf4\x83\xb6\x38\xe6\x46\x3a\x97\xc5\xc4\xf7\x7f\x21\ +\xaf\xbd\xd2\xe0\x2c\x66\x13\x18\x5d\xf9\x59\x3d\x87\x00\x60\x6b\ +\x56\x48\x72\x8b\x00\x08\xd0\xfa\x67\x10\x82\x6b\x88\x02\x42\xfb\ +\x17\x5a\x6d\x74\x5c\x8a\xed\xf9\x67\x25\xac\x77\xe6\x58\x97\xea\ +\x31\xfe\xf5\x7e\x98\x68\xbc\x76\x6f\xae\xd0\xc8\xe5\xc6\x7e\x78\ +\x61\xed\x36\xaf\x4e\xcf\x7d\x3a\xec\x74\x1b\xa2\x21\x0b\x65\x39\ +\x7f\x56\xba\xcb\x6d\x79\xfa\x35\xac\x8c\x12\x84\xca\x32\x5d\x5b\ +\xed\x5c\xc5\x9c\x35\xd5\xa8\xcc\xb6\xd7\x50\x44\x66\x9a\x1b\xc9\ +\xc7\x4e\x7a\xd9\x23\xcb\x98\x28\xab\xcc\xce\xab\x8b\xda\x37\x58\ +\x3b\xb9\xf3\x96\x2e\xbb\x46\x7b\x0f\x60\x5d\xbe\xd3\x9d\x29\x94\ +\xc1\xb6\xa5\x56\x79\x5d\xde\x87\x90\x88\x38\xe6\xe1\x70\x72\x94\ +\xee\x6b\x9a\x22\x88\xc7\xd9\xfa\x7b\xf7\xa4\xa6\x45\x42\xcc\x20\ +\x90\xe7\xae\xe6\x88\x24\xb7\x05\xc0\x3f\x59\xa3\x6c\xf8\xab\x3b\ +\x12\xed\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\xb9\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\ +\x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x02\x36\x49\x44\ +\x41\x54\x78\xda\x8d\x93\x4b\x68\x13\x51\x14\x86\xff\x99\xa4\x8d\ +\x12\xcd\x22\x2a\x51\x31\xad\xa4\xf5\x85\x56\x11\x44\xa9\x82\x14\ +\xb4\x2e\x02\x45\x21\x88\x0b\x37\x82\x1b\xf7\xa2\xa8\x28\xda\xba\ +\x93\x22\x92\x16\xb5\x50\x1b\xba\x71\xa1\xb8\xa8\x95\x22\x3e\x16\ +\x6e\x8a\x56\xac\x9a\x88\x91\xd0\xa4\xa0\x74\x9c\x49\x9a\x57\xf3\ +\xec\x4c\x26\xc7\x7b\xad\x03\xce\x10\x8b\x1f\xfc\x5c\x38\x33\xe7\ +\xbb\xe7\x32\x77\x04\x22\x82\x20\x08\x4e\x00\x3e\xfc\x0b\xa7\xc7\ +\x81\x03\xe7\x4e\x41\x68\xf6\x42\x2f\x7f\xc7\xc7\xd0\x63\xe4\xa5\ +\xaf\x44\x54\xb2\x63\x09\x1f\x11\x85\xd1\x80\x2f\x52\x11\xa7\xef\ +\xbc\x81\xbf\xb3\x1d\xfb\x3d\xc0\x94\x02\x4c\x4c\x1d\xba\x10\x7e\ +\xf5\xe4\x0c\x80\x51\xf0\x09\x18\x1d\xc4\xf8\x29\xcb\xa4\x24\x53\ +\x34\x9f\x4e\x53\x26\x9b\x25\x29\x99\x26\xd7\xc9\x21\x1a\x8f\xa4\ +\xa8\xbc\xa8\xd2\xc0\xe0\x20\x65\x72\x0b\x74\xeb\xe1\x24\x89\x3d\ +\x03\x45\x1c\xbd\xb7\x42\xc4\x5f\xd8\x6c\x36\x16\x11\xa2\x20\x40\ +\x60\x09\x8e\x4d\xc3\xe5\xdb\x82\xae\x36\x17\x6a\x9a\x8a\xf9\x54\ +\x0a\x92\x3c\x87\xc0\xc1\x4d\xd8\xb0\x7d\x87\x13\xb6\xfc\x65\x93\ +\x40\x14\xc5\xa5\x30\x11\x97\x84\x67\x15\x74\x77\xac\x07\xa0\xa3\ +\x50\x2c\x21\x18\x0c\x22\x70\xfc\x04\x8e\x74\x1d\x66\x75\x0f\x2b\ +\xab\x7b\xed\xd6\x09\xf8\xce\x5c\x22\x00\xd8\xb8\x6e\x15\xc6\x3f\ +\x49\xe8\xef\xf1\xa2\x5c\x2a\x61\x38\x34\x02\xb7\x7b\x0d\xda\xda\ +\xb7\x61\xdf\xed\x30\xd0\xa4\xcf\x35\x9e\x80\x49\xb8\xec\xbc\x7f\ +\x17\x72\xd5\x2c\xee\xbe\x4e\x40\x27\xa0\xa5\xa5\x15\x0e\x4f\x2b\ +\x46\xdf\x29\x48\x16\xd2\x1a\x3e\x3f\xea\x37\x4d\x60\x9c\xdd\x10\ +\xf9\x36\x7b\x71\x71\xe7\x7b\xdc\x7c\xfb\x03\xc1\xe7\x45\x1c\xdb\ +\xea\xc6\xcb\x99\x38\xf2\xab\x1d\xc0\xe4\xc8\x30\xa4\x68\xc2\x34\ +\x81\xc0\x1b\x2d\xb9\x76\x36\x80\xa7\xdd\x2b\xb1\x67\x6d\x06\x1f\ +\xa4\x38\x3a\xc5\x6f\x78\xe6\x77\x01\x91\x17\xf7\x89\x61\x5c\x24\ +\xfe\x19\xc3\x95\x4a\xc5\x98\x80\xaf\xa6\xfc\x79\x0f\x0c\xe3\xf9\ +\x6e\x56\x8b\x98\x8e\xc0\x8a\xcb\x46\xd7\x75\x96\x3a\x2a\xaa\x56\ +\x07\xa0\x83\xd1\x50\x60\x95\x71\xf8\x74\xb2\x2c\xc3\xde\xdc\x84\ +\xa1\x07\xa1\x09\x00\x51\x30\x4c\x37\xb1\x5a\xad\x92\xaa\xaa\xa4\ +\x69\x1a\xd5\x6a\x35\x62\x3b\x52\xbd\x5e\xff\x5d\x8b\xc5\x62\x6c\ +\x5d\xa4\xab\x7d\x7d\xd3\xdc\xcf\xfb\x58\xcc\x02\xa3\xd9\x2a\x58\ +\x28\x14\x68\x26\x11\xa7\x2b\xbd\xbd\xa6\x66\xb3\x60\x19\x94\x74\ +\x4e\xbf\x74\xfd\xc6\x98\xb5\x99\xe7\xff\x7e\x67\x40\x67\x89\x12\ +\x03\x16\x7e\x01\x57\xcf\x68\x6a\x5b\x49\x87\x93\x00\x00\x00\x00\ +\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\x33\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\ +\x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\xb0\x49\x44\ +\x41\x54\x78\xda\xa5\x93\x3f\x48\xc3\x40\x18\xc5\x5f\xa2\xd8\x74\ +\xab\xda\xc1\x3a\xd6\x55\xc4\xcd\x41\x10\xff\xd0\xad\x45\xa1\xe2\ +\x24\xae\x8e\xea\xe0\x20\xc5\x4d\xb0\xe8\xac\xb8\xbb\xa8\xb8\xd9\ +\xc1\x41\x67\x37\x05\xc7\x42\xc1\x0e\xd6\x52\x41\xcd\x35\x7f\x9a\ +\xb6\x97\x78\x5f\x22\xe1\x42\x1d\x0a\x7d\xb9\x7c\x97\xc0\xbd\xdf\ +\x3d\xbe\x5c\x14\xcf\xf3\x30\x88\x86\xa9\xe4\xb2\xd9\xef\x56\xcb\ +\x49\xec\xec\xed\x43\x96\xa2\x52\x55\xa1\xaa\x0a\x14\x05\x50\x10\ +\xdc\xa7\x27\x45\x68\x5a\xec\xe7\xae\x54\x1a\xf5\x01\x96\x69\x25\ +\x26\x52\x13\x58\x58\x5c\x82\x27\x03\x80\xc0\x28\x0a\x41\xc4\xf0\ +\xe7\xdb\x9b\x2b\x54\x2a\x95\x44\x98\xc0\x75\x5d\x68\x31\x0d\xda\ +\xc8\x10\x2e\x1f\x5e\xa3\x29\xfc\x42\x23\x48\xb1\x95\x99\x01\xe7\ +\xdc\xf7\x84\x00\x2e\x5e\xba\xdd\xae\xa0\x03\x9b\x2b\xd3\xb4\x3e\ +\x4c\x42\x3d\x72\xbd\xbf\x59\x3c\x98\x4e\x87\xd6\xfa\x9e\x48\x02\ +\xa2\x36\xed\x36\x99\x43\x79\x01\x81\x06\x5c\x02\x04\x90\xde\x04\ +\x2e\xe7\x3e\xd5\x10\x80\xeb\xfb\x27\xc8\x0a\xbf\x12\xa5\x10\x57\ +\x3e\x33\x47\x6b\xc9\xd3\x9b\x80\x19\x36\x32\xf3\xb3\xb2\x1b\x9e\ +\x64\x26\x56\xd3\x74\xfe\x49\xe0\x72\x70\x41\x3d\xdc\xdd\x46\x9f\ +\x22\x8f\xd4\x44\x1e\x34\xb1\x7f\x05\x9e\x68\x0f\x3a\x1d\x90\x8e\ +\x8a\xc7\x88\xc7\xe3\x61\x7c\x3a\x03\x24\x8a\xbd\x91\x5f\xf7\xe7\ +\xa9\x74\x3a\xda\x83\x8e\xd8\xdd\x69\xb7\x41\x2a\x97\xcb\xb8\x38\ +\x3b\x87\x65\x5b\x38\x28\x14\x20\x2b\x35\x39\x89\xda\x47\x8d\x3e\ +\x21\x79\x24\x80\xd8\xdd\x71\x1c\x90\x18\x63\x30\x4d\x13\x86\x61\ +\x40\xd7\x75\xc8\xf2\x84\xd1\x15\xd1\x93\xc9\x24\xde\xaa\xd5\x48\ +\x13\xbf\x18\xd3\xc7\xe8\x34\x32\x9d\xc1\xb6\x6d\x4a\xd4\x03\x20\ +\x30\x6d\x66\x5b\x16\xb8\x9c\x40\xe1\x7c\x99\x35\x9b\x8f\xba\xce\ +\xc6\x1b\x9f\x0d\xe4\xd6\x56\x41\xaa\xd7\xeb\x90\xf5\x5e\xab\x51\ +\x0e\x3c\xbf\xbc\x40\x1d\x52\x7f\x20\x34\xf0\xef\xfc\x0b\xfb\xd2\ +\x1e\xf6\x17\x50\x4f\x5c\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ +\x60\x82\ +\x00\x00\x02\x63\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x6e\xba\x00\x00\x6e\xba\x01\ +\xd6\xde\xb1\x17\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd9\x0a\x16\ +\x0d\x00\x3a\x08\xf3\xbd\x44\x00\x00\x01\xe3\x49\x44\x41\x54\x78\ +\xda\x8d\x92\x3f\x68\x53\x51\x14\xc6\xbf\x73\xef\x7d\x7f\x92\x34\ +\x8d\x49\x15\x42\xc5\x14\x8b\x83\xe8\xa6\x4b\x90\x0a\x8a\x4b\x07\ +\x09\x0e\x4e\x0e\x42\x10\x5c\x1c\x1d\x9c\x9d\x9c\x05\xd7\x2e\x82\ +\x9b\x9b\x19\x3a\x18\x3a\xb4\x81\x88\x8b\x53\x51\x04\x21\x81\x56\ +\xa9\x22\xea\x4b\x1a\xfb\x5e\xee\xbb\x9e\xfb\x4c\x87\xa6\xde\xe0\ +\x0f\x3e\x1e\x9c\x77\xbe\x73\xbe\xfb\xee\x23\x4c\x28\x16\x8b\x0b\ +\x51\x14\xf9\x00\x08\xff\x46\xb3\xf6\x58\x06\x0e\x96\x8c\x03\xad\ +\xb5\xe9\x74\x3a\xaf\x00\x9c\x9b\x5e\xa0\xae\x3f\xf9\x68\x46\x31\ +\x10\xb1\x18\xd3\xed\x76\x49\x4a\x09\x36\xa1\x5e\xaf\xc3\xd2\x6a\ +\xb5\xd0\x68\x34\x6e\x6e\x6e\x6d\xd1\xd5\x95\x95\xfb\x00\x76\x31\ +\x81\xea\x8f\x3f\xc4\x6b\xf7\xce\x78\xa3\x38\xc5\xe5\xb3\x85\x23\ +\x91\x78\x39\x88\x08\xfd\x7e\x1f\xb5\x5a\x0d\x96\x17\x6f\x06\x78\ +\xba\xfe\x19\x4a\x12\x42\x05\x28\x41\xa0\xde\xd7\x03\x0c\x0e\xfe\ +\x0e\xe0\x04\x98\x4e\xd0\x6e\xb7\x51\x2a\x95\x10\x04\x01\xee\xac\ +\xde\x40\xd9\x3f\x85\x62\x28\xf1\xe8\x25\x0f\x02\x81\x86\xb1\xc6\ +\x90\x13\x30\xd6\x74\x2c\x41\xb3\xd9\x3c\x92\x6c\x6f\x10\x61\x94\ +\x68\x10\x01\x0a\xb0\x4f\xc2\x84\xe9\x04\xce\xda\xa1\x47\x09\x12\ +\x94\x0f\xf8\x25\x70\x2c\x01\x1c\x35\xdb\x9f\x63\xb1\x17\xb4\xfc\ +\x60\xc3\xfc\xdc\x4f\x80\x78\x1f\x03\xed\xf1\x26\xe5\xbc\x68\x62\ +\x69\x3d\xc6\x9c\xe4\x7e\x3f\x8f\x52\xde\x83\x22\x21\xc7\x0f\x6f\ +\x5d\x54\x5f\x76\x7a\xb8\xbb\x7a\x01\xdf\x87\xda\xf9\x27\x19\x56\ +\xa5\x20\xf1\x7c\x7d\x1b\xd5\xd3\x4b\x58\x7b\xfd\x7e\xac\x0a\xa1\ +\x4f\xbf\x46\x09\xde\xf5\x87\xb8\xf6\xed\x37\x76\x7f\x68\x08\xc7\ +\x80\x94\xb5\x78\x42\x66\xbd\x57\x2a\x09\xac\x57\x85\x81\x4f\x9e\ +\x12\x3c\x59\x21\xf4\x88\x8b\x12\x44\x8e\x04\x06\xb6\x27\xeb\xb5\ +\x1e\xf6\x66\x03\xb0\x30\xef\x63\xb1\x1c\x60\x3e\x27\xf8\x7e\x69\ +\xe6\x37\x20\x32\xb6\x37\xf3\x58\xaf\x0a\x7d\x45\xb5\x93\x39\xcc\ +\x89\x2a\xde\x6e\xef\x40\x08\x81\x59\xa4\x69\x8a\x4b\xe7\xab\xa8\ +\x54\x72\xb0\x5e\xba\xfd\xac\x67\x12\x73\x18\x9b\xf0\x7f\x98\xec\ +\x38\x1e\xe9\xcc\x51\x66\x2d\xcf\x74\xbb\x2f\xe5\xd3\x1f\x03\xf5\ +\xc1\x56\xb6\x09\x0d\x58\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ +\x60\x82\ +\x00\x00\x03\x22\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ +\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x06\xec\x00\x00\ +\x06\xec\x01\x1e\x75\x38\x35\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ +\xd8\x01\x03\x12\x35\x13\x24\x41\x46\xca\x00\x00\x02\xaf\x49\x44\ +\x41\x54\x38\xcb\x95\x92\x59\x48\x54\x61\x18\x86\xdf\xff\xec\xce\ +\x9c\x99\xd1\x99\xb1\x74\x74\x5c\xa6\x2c\x4b\xa4\xdd\x68\xca\x16\ +\x2c\x83\x0a\x8c\x24\x8d\x16\x23\x91\x08\xea\x22\xc2\xa0\x20\x8a\ +\x0a\x82\xa0\xe8\x22\x22\x28\x93\x2e\x0a\xb2\x81\x88\x92\xd2\x8b\ +\xa0\x05\x53\x88\x2e\x24\xb2\xc5\x2c\x4b\x31\x9d\xc5\xd4\x59\xce\ +\xcc\x59\xfe\xd3\x55\x51\x12\xa1\xdf\xed\xc3\xfb\xf0\xf1\x7d\x2f\ +\x30\x85\x79\x7c\xfb\xfc\xc1\x47\x4d\xc7\xdf\x6e\xa9\x58\xc1\x4c\ +\x66\xcc\x54\x04\x7a\x2a\xd6\xe0\x74\xb9\xba\x5b\x9f\x74\xd2\x69\ +\x0b\x9e\x3d\xbc\x5e\x26\x30\xe6\x42\x03\x7c\xd3\xbf\xf8\x7f\x05\ +\x17\x8f\xed\x75\x06\x87\xbf\x9f\x83\x69\x7e\x6a\x6e\x69\x7f\x3a\ +\x2d\x41\xf5\xfa\xa5\x2c\x2f\x8a\x87\x48\x3c\x5c\xf1\xb9\x7f\xb8\ +\xe7\xc6\x9d\x47\x74\xca\x82\x8b\x47\xb7\xbb\x76\x56\xf9\xbb\xe6\ +\xe4\x65\x9c\x8e\xc6\x53\x60\x23\x51\xf1\x1e\xc0\xfd\xe2\xf7\x08\ +\xc9\x7a\xe0\xf7\xaf\x07\x00\x76\x72\xb8\xa4\x04\xa8\x58\xba\xec\ +\xbe\xc7\x93\x5d\xae\xa8\x3a\x58\x35\x0a\x97\x68\xc9\x11\x83\xb1\ +\x9c\xd5\xa1\x48\x5b\x35\x30\xb3\x60\xed\xca\xbb\xb3\x16\x2d\xd8\ +\x53\xde\xd3\x33\xc0\x4d\x16\xec\xab\xaa\xaf\xb5\xdb\xd2\x2a\x95\ +\x44\x1c\x48\xa5\x40\xa9\x0e\xaa\xe8\xa6\xaf\xbc\x7c\x53\x77\x52\ +\x69\x2a\x74\xbb\x66\xcf\x98\x5b\x92\x67\x1a\x26\x84\x1c\xcf\x66\ +\xf2\x67\xf8\xcc\x81\x8d\xb2\x35\x4d\xec\x9e\x91\x5b\xe0\x63\x08\ +\x80\x78\x04\x0c\xcb\x40\xfa\x18\x1c\xcb\x1d\x50\xa3\x9a\xc4\x43\ +\xe2\x04\xc0\x92\x86\xbe\xae\x8e\x8e\x1f\x5f\x86\xea\xff\xda\x20\ +\xd3\x21\x5d\xb3\xd8\x6c\x3e\x99\xd3\x10\x1e\x1d\x47\x22\xa1\x41\ +\x07\x01\xf3\x2d\x74\x55\x0c\x46\x76\x8b\x43\x51\x28\x02\x8f\x11\ +\x49\x1f\x0b\xf6\x47\xf6\x1f\x06\x94\xdf\x37\xb8\x79\x72\xdb\x51\ +\x30\xec\x91\xcc\x0c\x3b\x26\xc6\xa2\xd0\xd4\x24\x5c\x32\x07\x81\ +\x67\x20\xbf\xfa\x30\xd3\x37\x18\x16\x34\x9e\x10\xdb\x44\x0c\x6e\ +\x40\x0a\xd9\xac\x85\xcb\xe3\xc9\x36\x16\x00\x9a\x4f\x6c\xdd\x91\ +\xe9\x94\xaf\x24\x12\x3a\x03\x6a\xc0\x2a\x71\xd0\x35\x15\x76\xbb\ +\x05\x9a\xaa\x9b\x65\xef\x87\xa8\x91\x6e\x67\x6e\x39\xac\x3d\x23\ +\xe9\x0e\x7e\x4d\x70\x5c\x2a\x10\x85\xa2\x89\xe2\xe2\x02\xe6\x54\ +\xfd\xba\xc5\xb2\x55\x6c\xec\xfd\x1a\xe2\xf2\xb2\x1d\xa6\x95\x63\ +\xa1\xa8\x29\x64\xbb\x6d\x00\x28\x32\x24\x9e\xc6\xf3\x3c\x4c\xbb\ +\xd3\xfe\xae\xac\xb2\xf4\xf8\xa8\x84\x86\xb6\x7c\x6f\x38\xe9\x72\ +\x23\x34\x38\xf8\x86\x9b\x5f\xe4\x9d\x00\x54\x5a\x9a\xef\x31\x19\ +\x83\x42\x92\x58\x83\x81\xc8\x5a\x25\x16\x34\x09\xf0\x92\xf0\xb6\ +\x93\xd3\xbb\xbc\x7e\x5f\x93\xc5\x29\xf7\x6e\xd8\xb5\x2a\xda\x77\ +\xf9\x45\x6d\x6b\x38\xbc\xb8\x71\x7c\xbc\x85\x04\x2e\xd4\x59\xa8\ +\x61\xf8\x89\x89\x6a\x01\xc4\xc7\x89\xc4\x2b\xf0\x4c\x2e\x55\x35\ +\x19\x94\x4d\xa9\x2c\xdb\x6c\x18\xe4\xac\xa6\x93\x70\xe0\x75\x54\ +\x0f\x04\x02\x7f\xbd\x9d\x00\x40\xeb\xa5\x3a\xd6\xa4\x44\x34\x28\ +\xb1\x9b\x84\x7a\x35\x83\xce\x8b\xc7\xe2\xb3\xb2\x1c\xb2\xd2\x1b\ +\x52\x9e\x67\x2d\x21\x2f\x6b\x6a\x02\xff\xac\xfc\x4f\x89\x4f\x0d\ +\x65\xb8\x03\x8d\x9c\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ +\x82\ +\x00\x00\x01\xf3\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x04\x00\x00\x00\xb5\xfa\x37\xea\ +\x00\x00\x00\x02\x73\x42\x49\x54\x08\x08\x55\xec\x46\x04\x00\x00\ +\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\x01\x3a\ +\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\ +\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\x65\ +\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x72\x49\x44\x41\x54\ +\x28\xcf\x45\x91\x4f\x2b\x04\x71\x1c\xc6\xbf\xf3\x32\xe4\x6c\xde\ +\x80\xa2\x44\x39\xbb\xec\x1f\xb3\x23\x6d\x9b\x25\x8b\x10\xfd\x76\ +\x36\xec\xa2\x31\x89\x13\x0e\x2e\xca\xfb\x98\x4d\x7b\xf0\x12\xb4\ +\x85\x83\x36\x17\x97\xb1\xa2\xe9\x47\xfd\x0e\x63\xb6\x8f\x83\xb5\ +\x9e\xe7\xf6\xf4\xf4\x1c\x9e\x8f\x20\xbf\x3e\xb1\x8f\x83\x20\xf4\ +\x23\x3f\x0a\xc2\xe3\xe0\xc4\xfe\xcb\x05\x41\x7c\xeb\x50\xd5\x4d\ +\x95\x1a\x3e\x3e\x35\xaa\xd4\xcd\xa1\xf2\xad\x7e\xa1\x61\xd5\x9b\ +\xdb\xec\xd2\xa2\xcd\x13\x1d\x1e\x68\xb1\xc3\x36\xf5\x66\xc3\x42\ +\x04\xf1\xd4\x06\x17\xdc\xf3\x4a\xcc\x27\x5f\x68\xde\x78\xe6\x9c\ +\x0d\x3c\x85\x88\xb2\x37\x8d\xc7\x23\x1f\x18\xbe\xe9\xd1\x23\x25\ +\x41\xf3\x42\x8d\x4d\xa3\x6c\x59\x0f\x16\xb9\xe5\x1d\xc3\x15\x67\ +\x1c\xa0\xd8\x42\x91\xa2\x69\xb3\xc8\xfa\x91\x54\xc2\x15\xee\xd0\ +\xa4\x5c\xd2\xa0\x82\x4b\x8e\x59\x20\xa1\xcb\x1a\x95\x50\xca\x91\ +\x47\x9b\x98\x94\x3d\x96\xc9\x31\xc5\x38\xd3\x40\x4a\xcc\x3e\xe5\ +\x48\x4a\x51\x95\x47\x34\x3d\xb2\x8c\x32\x84\x30\xc4\x28\xd0\x43\ +\x73\x40\x29\x92\x62\x58\xee\x2f\xb8\x4c\x30\xc2\x30\x23\x4c\xf4\ +\x17\x96\x29\x86\x52\x0c\x1c\x6e\xe8\x92\xb0\xc4\x0c\x93\x8c\x31\ +\xc9\x0c\x90\xd0\xc1\xa1\x78\x24\xf3\x76\xc1\xac\xf2\x8c\x26\xe5\ +\x5f\x29\x1a\x8f\x82\x99\xb7\x05\x71\x55\x96\x53\x5e\xd0\x24\xa4\ +\x83\x1f\xae\xc9\xe2\x2a\x44\x90\x39\xcb\x6d\x66\x58\xa3\x4d\x97\ +\x18\x4d\x4c\x87\x2a\x19\xdc\xe6\x9c\xd5\x87\xe5\x5a\x8e\xca\x9b\ +\x0c\x25\x76\xd8\x63\x81\x0c\x79\xe3\x28\xd7\x1a\xd0\x44\x90\x82\ +\xed\x04\xf9\x30\x17\xe5\xa2\x7c\xe8\x04\x85\x01\xee\x1f\x8d\x42\ +\x66\xb3\xf5\x8b\xfe\x99\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ +\x60\x82\ +\x00\x00\x03\xa3\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\ +\x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x03\x20\x49\x44\ +\x41\x54\x78\xda\x6d\x93\xcd\x6b\x5c\x55\x18\xc6\x9f\xf7\xdc\x73\ +\xee\xd7\x24\x33\x49\x27\xa9\x49\x27\xa1\x4d\x86\xd6\x18\x34\x4e\ +\x28\xad\x8d\x42\x68\xfd\xa0\xd0\xa2\xfe\x01\x45\xb0\xb8\x75\xa3\ +\xe0\xd2\x55\x45\x37\x0a\xba\x13\x37\x82\x3b\x17\x12\x14\x14\x8d\ +\x45\x51\x6a\x21\x15\xa3\x26\x25\xc1\x8f\x36\x89\x69\xd2\x74\xec\ +\x24\x73\x33\x9d\xfb\x71\xce\x3d\xf7\x78\x47\x0c\x66\xd1\x1f\x3c\ +\xbb\xf3\xbc\xe7\xe5\x79\xdf\x97\x8c\x31\xd8\x0f\x3d\x47\x55\x30\ +\x5c\xb0\xba\xac\x27\x47\x2a\x23\x93\xe5\xae\x32\x5b\xd9\x5c\x59\ +\xaa\x6f\xd5\x2f\x43\xe3\x43\xf3\x99\xf9\xf3\xbf\x77\x43\x00\x78\ +\xa7\xc0\x9e\x91\x81\xf0\x4a\x79\xb4\xfc\xc6\xf9\x13\xe7\xdd\x4a\ +\x7f\x05\x83\x7d\x83\xb0\x98\x85\x20\x08\x70\xe3\xf6\x0d\xcc\x5c\ +\x9d\x89\xb7\xd7\xb6\x2f\x21\xc3\x8f\xa5\x4a\xf1\x93\x24\x48\xe2\ +\x4e\x81\x7f\xcd\xcc\xa1\x6f\xa7\x4e\x9d\x9a\x3a\x33\xf9\x94\x60\ +\x36\xc3\xfd\xd0\x52\xe3\xea\xc2\x15\xf5\xeb\xfa\x2f\x74\xf1\xe9\ +\x97\xf8\x7b\x1f\xbf\x0b\x8e\x1c\x62\x78\xb5\x36\xf6\xf0\xd4\x89\ +\xb1\xc7\x44\x10\x06\xa0\x90\xd0\x68\x34\xb0\x71\x7b\x03\x64\x08\ +\xd5\xe1\x2a\xbc\x1e\x0f\x1d\x26\x8e\xd6\xc4\x44\xb5\x86\xcc\x18\ +\x30\x10\x78\xfe\xfb\x83\xfd\x03\x3d\x97\x8e\x1e\x19\x17\x8d\x66\ +\x03\x3a\xd5\x98\xff\x79\x1e\x7f\xac\xfc\x0e\x47\x0b\x78\xb6\x87\ +\x2b\x73\xdf\xa3\xb7\x52\xc6\xc9\xe3\x27\xd1\x5b\xec\x05\x0c\x60\ +\x19\x0b\x8c\x18\x38\x11\x5e\xa8\x94\x86\x9c\x76\xdc\x46\x47\x8b\ +\xd7\x17\xf1\x77\x7d\xd3\x14\x5c\x21\x43\xa9\x2e\x27\x99\xd2\x3e\ +\x13\x67\x9b\x71\xc3\x9e\x5d\x9d\xa5\xe9\xf1\x69\xb8\xc6\x05\x49\ +\x82\x45\x04\xee\xf8\xe2\x8c\xd1\xa0\xed\xe6\x36\x76\x83\x5d\x6c\ +\xdc\x5a\x87\xef\x8b\x68\x37\x4a\x1e\xed\x24\x9e\x77\x38\x09\x0b\ +\xcf\x98\x47\x88\x1e\x38\x7c\x10\x2d\xde\x42\x74\x2f\x42\xb0\x13\ +\x80\x32\x18\x2e\x1c\x56\x8b\xb5\x42\x1a\x04\xa8\xdf\xad\xc3\xd2\ +\x0c\x6d\xa9\xbe\xde\x1b\x57\x97\xe7\xf6\x89\x22\xf3\x44\xaf\x87\ +\xf4\x4e\x82\xb5\xe0\x26\x5a\x77\x5a\x30\xed\x0c\x69\xac\xbf\x24\ +\xff\x82\x90\x59\xdb\x12\xe0\x80\x72\x14\x84\x22\x48\xa9\x67\xf5\ +\x4c\x76\x76\xdf\x6e\xb8\x00\x4a\x02\x16\x81\x8c\xce\x72\x69\x63\ +\x94\xf9\xd4\xb4\x98\x4c\xf4\x35\x84\x29\x62\x11\x43\x97\x35\xcc\ +\x28\xe0\xb8\xd6\x74\x27\x5c\xfc\xcf\x21\x46\xf4\x11\x4d\x50\x9f\ +\xae\x65\x5c\x0c\x5b\xaa\xbb\xe8\xc4\xc8\xe1\xa9\xcc\xbe\x70\x6d\ +\xf1\x38\x0e\xa4\x84\x3e\x20\xeb\xce\x50\x2e\x0a\x87\x96\xd8\x4f\ +\xec\x79\xfa\x1c\x44\xe4\xd8\xfc\x5c\x69\xd8\x9d\x4b\x87\xb2\xd3\ +\x89\xa2\xd5\xb8\x94\x6e\x5a\xcb\x6c\x8b\x2e\xd2\x26\xe1\x59\x3c\ +\xe4\xb9\x62\x1e\x23\x70\xe5\x40\x0a\x5f\xd8\x28\x38\x36\xca\xdc\ +\x87\x6a\x68\x63\x81\xe0\x0d\xf0\x34\xa4\xf4\xaf\x58\xa5\x8b\xa1\ +\x94\xcb\xaa\x95\xad\x45\xbf\xa9\xaf\xa2\x0f\xd4\x2a\xcb\xc3\x5a\ +\x8e\x62\xf5\xba\xbf\x23\x94\xcb\x3b\x73\xe7\x79\x01\x01\xee\x13\ +\x0a\xc7\x6c\xb2\x26\x18\xc9\xfe\x4c\x14\x7c\xbb\xda\xe5\xd8\xe3\ +\x25\xdb\x1f\x4b\x16\xf4\x6b\xf1\x56\x7a\x04\x39\x7b\x3b\xfb\x4e\ +\x63\x2b\xfc\xa6\xe7\xa6\x9b\xf4\x30\x0f\xae\x10\xa0\x02\x61\xf7\ +\x60\x8c\x9d\x43\x11\x9a\x83\x31\x5a\xfd\x09\x8a\xdc\xa9\x46\xd7\ +\xe5\xe9\xb0\x29\x97\x00\x7c\x87\x9c\xfd\xc7\x44\x00\x5e\xee\x3e\ +\xe0\xbe\xe5\x8d\x72\xaf\x32\x56\x64\xf1\xb1\x14\xed\xb2\x84\x75\ +\x8b\xe1\xee\x5c\xdb\xe8\x6b\x59\x18\xde\x53\x6f\x02\x78\x3b\xef\ +\x5c\x22\xe7\x7e\xe7\x7c\x18\xc0\x8b\xb9\x9e\xb0\x18\x1d\x37\x06\ +\x94\x19\xb3\x00\xe0\x87\x5c\xef\xe7\xc6\x75\xec\xe3\x1f\x51\x72\ +\x6b\x1e\x49\x70\xdf\x2c\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ +\x60\x82\ +\x00\x00\x01\xe5\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x06\xec\x00\x00\x06\xec\x01\ +\x1e\x75\x38\x35\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd8\x03\x14\ +\x12\x23\x3b\xba\x62\x67\xa1\x00\x00\x01\x65\x49\x44\x41\x54\x78\ +\xda\x95\x90\xcf\x4a\xc3\x40\x10\xc6\x67\x37\xa6\xa5\x81\x5e\x8a\ +\xa7\x3c\x86\x78\xf1\x01\xd4\x1e\xac\x6f\xe0\xc5\x73\xbd\xd8\x53\ +\x2d\xf8\x08\xbe\x4b\xcf\x3e\x80\xa0\x10\x3c\xfb\x00\x56\xda\x54\ +\x4a\x82\x25\x7f\x77\x3a\xb3\x6c\xe8\x9a\x90\xa2\x1f\x7c\x7c\xbb\ +\xec\xcc\x6f\x77\x56\x00\xc0\x80\x7c\x46\x16\x70\x58\x48\x7e\x21\ +\x7f\x43\x4d\x57\xf8\x37\x31\x60\x04\x35\x1d\x55\x37\xaf\xc2\x10\ +\xa4\x94\x20\x85\x00\x41\x29\x38\xd9\xa6\xb0\xdf\xef\x73\x88\x26\ +\xc0\xc8\x71\x1c\x0d\x88\xa3\x08\x82\x20\x00\x2d\x0b\xd0\xed\x76\ +\x39\x4e\xc8\xa9\x35\xd2\x2b\x2f\x46\x48\xda\x6c\x36\x18\xc5\x31\ +\xce\xe7\x73\xfc\xc7\x48\xc3\xc6\x0b\xf8\x56\x56\xb8\x5e\x83\xc3\ +\x23\x19\x0b\xb2\x3d\x92\xe7\x79\x1c\xb2\x02\xe8\x22\x0d\x31\x00\ +\xdd\x4c\x7b\x9d\x55\x33\x67\xdb\x1f\x48\xab\xd0\xec\xf7\x10\xeb\ +\x53\xa1\x15\x60\x15\xd9\x40\xa7\x06\xa6\x6c\x00\xea\x07\x36\x60\ +\x0f\x3e\x0c\x68\x42\x44\xad\xb9\x28\x0a\x08\xc3\x10\x3a\x1d\x17\ +\x7a\x3d\x0f\x14\x08\xa4\xb2\x82\x01\x8a\x0e\xd1\x75\x5d\x51\xdd\ +\xac\xb3\x76\x2b\x37\xfb\xbe\x0f\xac\xed\xf6\x07\x1e\x67\xb3\x67\ +\x5a\x06\xbc\x3f\x26\xdf\x90\x6f\x8d\x9f\x90\x94\x24\x09\x66\x59\ +\x86\x0c\x2f\xcb\x12\x17\x5f\x0b\x54\x4a\x61\x1c\xc7\x38\xbe\x1b\ +\xbf\x51\xdd\x29\xb4\x68\xc8\x80\x34\x4d\x31\xcf\x73\x06\xe8\xc6\ +\xe5\x6a\xa9\x9b\x27\x93\xfb\x77\xaa\xb9\x26\xcb\x36\xc0\x05\x35\ +\x2a\xb4\x94\x15\x25\x7e\x2e\x57\x38\x7d\x98\xf2\x93\x2f\x7f\x7d\ +\x3e\x34\x35\x20\x9f\x9b\x22\x5b\x68\x66\xfe\x20\x2b\x30\xda\x01\ +\x68\x0b\xe8\x88\x71\x7b\x2b\x7d\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ " qt_resource_name = "\ @@ -54,16 +530,82 @@ qt_resource_name = "\ \x07\xac\xfa\xc2\ \x00\x74\ \x00\x65\x00\x78\x00\x74\x00\x65\x00\x72\ +\x00\x06\ +\x07\x03\x7d\xc3\ +\x00\x69\ +\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ \x00\x08\ \x0a\x61\x5a\xa7\ \x00\x69\ \x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x15\ +\x0f\x21\xb3\x87\ +\x00\x67\ +\x00\x6f\x00\x2d\x00\x6e\x00\x65\x00\x78\x00\x74\x00\x2d\x00\x76\x00\x69\x00\x65\x00\x77\x00\x2d\x00\x70\x00\x61\x00\x67\x00\x65\ +\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x19\ +\x0d\x16\x46\x27\ +\x00\x67\ +\x00\x6f\x00\x2d\x00\x70\x00\x72\x00\x65\x00\x76\x00\x69\x00\x6f\x00\x75\x00\x73\x00\x2d\x00\x76\x00\x69\x00\x65\x00\x77\x00\x2d\ +\x00\x70\x00\x61\x00\x67\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x10\ +\x0c\xbc\x2e\x67\ +\x00\x64\ +\x00\x6f\x00\x63\x00\x75\x00\x6d\x00\x65\x00\x6e\x00\x74\x00\x2d\x00\x6e\x00\x65\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x10\ +\x08\x12\xae\xa7\ +\x00\x6d\ +\x00\x65\x00\x64\x00\x69\x00\x61\x00\x2d\x00\x72\x00\x65\x00\x63\x00\x6f\x00\x72\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x18\ +\x00\x8c\x38\x27\ +\x00\x64\ +\x00\x6f\x00\x63\x00\x75\x00\x6d\x00\x65\x00\x6e\x00\x74\x00\x2d\x00\x6f\x00\x70\x00\x65\x00\x6e\x00\x2d\x00\x72\x00\x65\x00\x63\ +\x00\x65\x00\x6e\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x11\ +\x0f\xe3\xd5\x67\ +\x00\x64\ +\x00\x6f\x00\x63\x00\x75\x00\x6d\x00\x65\x00\x6e\x00\x74\x00\x2d\x00\x73\x00\x61\x00\x76\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x16\ +\x04\x98\x82\x67\ +\x00\x64\ +\x00\x6f\x00\x63\x00\x75\x00\x6d\x00\x65\x00\x6e\x00\x74\x00\x2d\x00\x6f\x00\x70\x00\x65\x00\x6e\x00\x2d\x00\x64\x00\x61\x00\x74\ +\x00\x61\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x0d\x8b\x39\xe7\ +\x00\x65\ +\x00\x64\x00\x69\x00\x74\x00\x2d\x00\x63\x00\x6c\x00\x65\x00\x61\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x17\ +\x09\x10\x6a\x47\ +\x00\x6d\ +\x00\x65\x00\x64\x00\x69\x00\x61\x00\x2d\x00\x70\x00\x6c\x00\x61\x00\x79\x00\x62\x00\x61\x00\x63\x00\x6b\x00\x2d\x00\x73\x00\x74\ +\x00\x6f\x00\x70\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x10\ +\x08\x15\x13\x67\ +\x00\x76\ +\x00\x69\x00\x65\x00\x77\x00\x2d\x00\x72\x00\x65\x00\x66\x00\x72\x00\x65\x00\x73\x00\x68\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0d\ +\x01\x1c\xb1\xa7\ +\x00\x65\ +\x00\x64\x00\x69\x00\x74\x00\x2d\x00\x63\x00\x6f\x00\x70\x00\x79\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ -\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\ +\x00\x00\x00\x12\x00\x02\x00\x00\x00\x0b\x00\x00\x00\x04\ +\x00\x00\x00\x24\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x4b\ +\x00\x00\x01\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x1c\x6a\ +\x00\x00\x01\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x11\x3f\ +\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x09\x68\ +\x00\x00\x01\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x18\xc3\ +\x00\x00\x01\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x16\xcc\ +\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x07\x21\ +\x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x04\xb3\ +\x00\x00\x01\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x13\xa6\ +\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x02\x30\ +\x00\x00\x01\x24\x00\x00\x00\x00\x00\x01\x00\x00\x0f\x08\ " def qInitResources(): diff --git a/texter/texter/texter_ui.py b/texter/texter/texter_ui.py index cb7f74b..66a337d 100644 --- a/texter/texter/texter_ui.py +++ b/texter/texter/texter_ui.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'texter3.ui' +# Form implementation generated from reading ui file 'texter.ui' # -# Created: Mon Apr 21 22:38:51 2014 +# Created: Sat May 10 18:57:19 2014 # by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -26,7 +26,7 @@ except AttributeError: class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) - MainWindow.resize(1475, 651) + MainWindow.resize(1475, 592) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) @@ -47,6 +47,11 @@ class Ui_MainWindow(object): brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) MainWindow.setPalette(palette) + font = QtGui.QFont() + font.setFamily(_fromUtf8("Helvetica")) + font.setPointSize(8) + font.setBold(True) + MainWindow.setFont(font) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/texter/icon.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) MainWindow.setWindowIcon(icon) @@ -102,7 +107,7 @@ class Ui_MainWindow(object): self.live_text.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.live_text.setAcceptRichText(True) self.live_text.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByKeyboard|QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextBrowserInteraction|QtCore.Qt.TextEditable|QtCore.Qt.TextEditorInteraction|QtCore.Qt.TextSelectableByKeyboard|QtCore.Qt.TextSelectableByMouse) - self.live_text.setRichTextSupport(KRichTextWidget.RichTextSupportValues(KRichTextWidget.SupportAlignment|KRichTextWidget.SupportChangeListStyle|KRichTextWidget.SupportFontFamily|KRichTextWidget.SupportFontSize|KRichTextWidget.SupportTextForegroundColor)) + self.live_text.setRichTextSupport(KRichTextWidget.RichTextSupportValues(KRichTextWidget.SupportAlignment|KRichTextWidget.SupportFontFamily|KRichTextWidget.SupportFontSize|KRichTextWidget.SupportTextForegroundColor)) self.live_text.setObjectName(_fromUtf8("live_text")) self.horizontalLayout.addWidget(self.live_text) self.preview_text = KRichTextWidget(self.centralwidget) @@ -112,7 +117,7 @@ class Ui_MainWindow(object): sizePolicy.setHeightForWidth(self.preview_text.sizePolicy().hasHeightForWidth()) self.preview_text.setSizePolicy(sizePolicy) self.preview_text.setMinimumSize(QtCore.QSize(300, 582)) - self.preview_text.setMaximumSize(QtCore.QSize(769, 582)) + self.preview_text.setMaximumSize(QtCore.QSize(775, 582)) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) @@ -140,11 +145,7 @@ class Ui_MainWindow(object): self.preview_text.setRichTextSupport(KRichTextWidget.RichTextSupportValues(KRichTextWidget.SupportAlignment|KRichTextWidget.SupportChangeListStyle|KRichTextWidget.SupportFontFamily|KRichTextWidget.SupportFontSize|KRichTextWidget.SupportTextForegroundColor)) self.preview_text.setObjectName(_fromUtf8("preview_text")) self.horizontalLayout.addWidget(self.preview_text) - spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Minimum) - self.horizontalLayout.addItem(spacerItem) self.verticalLayout.addLayout(self.horizontalLayout) - spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.verticalLayout.addItem(spacerItem1) MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) From f3477757c21a048a072698a608bbc7d4548997e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Sun, 25 May 2014 15:52:40 +0200 Subject: [PATCH 3/7] intermediate commit --- config_files/tmux-sensor-testing.conf | 36 +- dump_grabber/dump_grabber/dump_grabber.ui | 858 ++++++++-------- dump_grabber/dump_grabber/dump_grabber_ui.py | 31 +- dump_grabber/dump_grabber/main.py | 239 ++--- ekgplotter/ekgplotter/main_qt.py | 312 ++++++ ekgplotter/setup.py | 2 +- sensors2osc/sensors2osc/common.py | 11 +- sensors2osc/sensors2osc/ehealth2osc.py | 25 +- sensors2osc/sensors2osc/ekg2osc.py | 10 +- sensors2osc/sensors2osc/main.py | 8 +- sensors2osc/sensors2osc/pulse2osc.py | 18 +- texter/texter/448_texter.db | Bin 5412 -> 0 bytes texter/texter/edit_dialog.ui | 929 +++++++++--------- .../{text_sorter_ui.py => edit_dialog_ui.py} | 54 +- texter/texter/favicon.ico | Bin 0 -> 766 bytes texter/texter/favicon.pnm | 5 + texter/texter/main.py | 29 +- texter/texter/text_model.py | 27 +- texter/texter/texter.html | 1 + texter/texter/texter_ui.py | 8 +- 20 files changed, 1377 insertions(+), 1226 deletions(-) create mode 100644 ekgplotter/ekgplotter/main_qt.py delete mode 100644 texter/texter/448_texter.db rename texter/texter/{text_sorter_ui.py => edit_dialog_ui.py} (85%) create mode 100644 texter/texter/favicon.ico create mode 100644 texter/texter/favicon.pnm create mode 120000 texter/texter/texter.html diff --git a/config_files/tmux-sensor-testing.conf b/config_files/tmux-sensor-testing.conf index ef21a6b..fff64ea 100644 --- a/config_files/tmux-sensor-testing.conf +++ b/config_files/tmux-sensor-testing.conf @@ -15,17 +15,17 @@ set -g terminal-overrides 'xterm*:smcup@:rmcup@' new-session -s 'csession' attach-session -t 'csession' -new-window -n 'socat-ekg-merle' -t 'csession:3' 'socat -d -d PTY,raw,echo=0,link=/tmp/ekg2osc-merle-in,b115200,user=stefan PTY,raw,echo=0,link=/tmp/ekg2osc-merle-out,b115200,user=stefan' -new-window -n 'socat-ekg-uwe' -t 'csession:4' 'socat -d -d PTY,raw,echo=0,link=/tmp/ekg2osc-uwe-in,b115200,user=stefan PTY,raw,echo=0,link=/tmp/ekg2osc-uwe-out,b115200,user=stefan' -new-window -n 'socat-ekg-bjoern' -t 'csession:2' 'socat -d -d PTY,raw,echo=0,link=/tmp/ekg2osc-bjoern-in,b115200,user=stefan PTY,raw,echo=0,link=/tmp/ekg2osc-bjoern-out,b115200,user=stefan' +new-window -n 'socat-ekg-merle' -t 'csession:3' 'socat -d -d PTY,raw,echo=0,link=/tmp/ekg2osc-merle-in,b115200,user=sarah PTY,raw,echo=0,link=/tmp/ekg2osc-merle-out,b115200,user=sarah' +new-window -n 'socat-ekg-uwe' -t 'csession:4' 'socat -d -d PTY,raw,echo=0,link=/tmp/ekg2osc-uwe-in,b115200,user=sarah PTY,raw,echo=0,link=/tmp/ekg2osc-uwe-out,b115200,user=sarah' +new-window -n 'socat-ekg-bjoern' -t 'csession:2' 'socat -d -d PTY,raw,echo=0,link=/tmp/ekg2osc-bjoern-in,b115200,user=sarah PTY,raw,echo=0,link=/tmp/ekg2osc-bjoern-out,b115200,user=sarah' -new-window -n 'socat-pulse-merle' -t 'csession:6' 'socat -d -d PTY,raw,echo=0,link=/tmp/pulse2osc-merle-in,b115200,user=stefan PTY,raw,echo=0,link=/tmp/pulse2osc-merle-out,b115200,user=stefan' -new-window -n 'socat-pulse-uwe' -t 'csession:7' 'socat -d -d PTY,raw,echo=0,link=/tmp/pulse2osc-uwe-in,b115200,user=stefan PTY,raw,echo=0,link=/tmp/pulse2osc-uwe-out,b115200,user=stefan' -new-window -n 'socat-pulse-bjoern' -t 'csession:5' 'socat -d -d PTY,raw,echo=0,link=/tmp/pulse2osc-bjoern-in,b115200,user=stefan PTY,raw,echo=0,link=/tmp/pulse2osc-bjoern-out,b115200,user=stefan' +new-window -n 'socat-pulse-merle' -t 'csession:6' 'socat -d -d PTY,raw,echo=0,link=/tmp/pulse2osc-merle-in,b115200,user=sarah PTY,raw,echo=0,link=/tmp/pulse2osc-merle-out,b115200,user=sarah' +new-window -n 'socat-pulse-uwe' -t 'csession:7' 'socat -d -d PTY,raw,echo=0,link=/tmp/pulse2osc-uwe-in,b115200,user=sarah PTY,raw,echo=0,link=/tmp/pulse2osc-uwe-out,b115200,user=sarah' +new-window -n 'socat-pulse-bjoern' -t 'csession:5' 'socat -d -d PTY,raw,echo=0,link=/tmp/pulse2osc-bjoern-in,b115200,user=sarah PTY,raw,echo=0,link=/tmp/pulse2osc-bjoern-out,b115200,user=sarah' -new-window -n 'socat-ehealth-merle' -t 'csession:9' 'socat -d -d PTY,raw,echo=0,link=/tmp/ehealth2osc-merle-in,b115200,user=stefan PTY,raw,echo=0,link=/tmp/ehealth2osc-merle-out,b115200,user=stefan' -new-window -n 'socat-ehealth-uwe' -t 'csession:10' 'socat -d -d PTY,raw,echo=0,link=/tmp/ehealth2osc-uwe-in,b115200,user=stefan PTY,raw,echo=0,link=/tmp/ehealth2osc-uwe-out,b115200,user=stefan' -new-window -n 'socat-ehealth-bjoern' -t 'csession:8' 'socat -d -d PTY,raw,echo=0,link=/tmp/ehealth2osc-bjoern-in,b115200,user=stefan PTY,raw,echo=0,link=/tmp/ehealth2osc-bjoern-out,b115200,user=stefan' +new-window -n 'socat-ehealth-merle' -t 'csession:9' 'socat -d -d PTY,raw,echo=0,link=/tmp/ehealth2osc-merle-in,b115200,user=sarah PTY,raw,echo=0,link=/tmp/ehealth2osc-merle-out,b115200,user=sarah' +new-window -n 'socat-ehealth-uwe' -t 'csession:10' 'socat -d -d PTY,raw,echo=0,link=/tmp/ehealth2osc-uwe-in,b115200,user=sarah PTY,raw,echo=0,link=/tmp/ehealth2osc-uwe-out,b115200,user=sarah' +new-window -n 'socat-ehealth-bjoern' -t 'csession:8' 'socat -d -d PTY,raw,echo=0,link=/tmp/ehealth2osc-bjoern-in,b115200,user=sarah PTY,raw,echo=0,link=/tmp/ehealth2osc-bjoern-out,b115200,user=sarah' new-window -n 'ekg2osc-merle' -t 'csession:11' 'ekgmerle -D /tmp/ekg2osc-merle-out' new-window -n 'ekg2osc-uwe' -t 'csession:12' 'ekguwe -D /tmp/ekg2osc-uwe-out' @@ -39,17 +39,17 @@ new-window -n 'ehealth2osc-merle' -t 'csession:17' 'sleep 1 && ehealthmerle - new-window -n 'ehealth2osc-uwe' -t 'csession:18' 'sleep 1 && ehealthuwe -D /tmp/ehealth2osc-uwe-out' new-window -n 'ehealth2osc-bjoern' -t 'csession:19' 'sleep 1 && ehealthbjoern -D /tmp/ehealth2osc-bjoern-out' -new-window -n 'test-ekg-merle' -t 'csession:21' 'python /home/stefan/dev/psychose/sensors2osc/sensors2osc/socat_ekg_test.py /tmp/ekg2osc-merle-in' -new-window -n 'test-ekg-uwe' -t 'csession:22' 'python /home/stefan/dev/psychose/sensors2osc/sensors2osc/socat_ekg_test.py /tmp/ekg2osc-uwe-in' -new-window -n 'test-ekg-bjoern' -t 'csession:20' 'python /home/stefan/dev/psychose/sensors2osc/sensors2osc/socat_ekg_test.py /tmp/ekg2osc-bjoern-in' +new-window -n 'test-ekg-merle' -t 'csession:21' 'python /home/sarah/dev/psychose/sensors2osc/sensors2osc/socat_ekg_test.py /tmp/ekg2osc-merle-in' +new-window -n 'test-ekg-uwe' -t 'csession:22' 'python /home/sarah/dev/psychose/sensors2osc/sensors2osc/socat_ekg_test.py /tmp/ekg2osc-uwe-in' +new-window -n 'test-ekg-bjoern' -t 'csession:20' 'python /home/sarah/dev/psychose/sensors2osc/sensors2osc/socat_ekg_test.py /tmp/ekg2osc-bjoern-in' -new-window -n 'test-pulse-merle' -t 'csession:24' 'python /home/stefan/dev/psychose/sensors2osc/sensors2osc/socat_pulse_test.py /tmp/pulse2osc-merle-in' -new-window -n 'test-pulse-uwe' -t 'csession:25' 'python /home/stefan/dev/psychose/sensors2osc/sensors2osc/socat_pulse_test.py /tmp/pulse2osc-uwe-in' -new-window -n 'test-pulse-bjoern' -t 'csession:23' 'python /home/stefan/dev/psychose/sensors2osc/sensors2osc/socat_pulse_test.py /tmp/pulse2osc-bjoern-in' +new-window -n 'test-pulse-merle' -t 'csession:24' 'python /home/sarah/dev/psychose/sensors2osc/sensors2osc/socat_pulse_test.py /tmp/pulse2osc-merle-in' +new-window -n 'test-pulse-uwe' -t 'csession:25' 'python /home/sarah/dev/psychose/sensors2osc/sensors2osc/socat_pulse_test.py /tmp/pulse2osc-uwe-in' +new-window -n 'test-pulse-bjoern' -t 'csession:23' 'python /home/sarah/dev/psychose/sensors2osc/sensors2osc/socat_pulse_test.py /tmp/pulse2osc-bjoern-in' -new-window -n 'test-ehealth-merle' -t 'csession:27' 'python /home/stefan/dev/psychose/sensors2osc/sensors2osc/socat_ehealth_test.py /tmp/ehealth2osc-merle-in' -new-window -n 'test-ehealth-uwe' -t 'csession:28' 'python /home/stefan/dev/psychose/sensors2osc/sensors2osc/socat_ehealth_test.py /tmp/ehealth2osc-uwe-in' -new-window -n 'test-ehealth-bjoern' -t 'csession:26' 'python /home/stefan/dev/psychose/sensors2osc/sensors2osc/socat_ehealth_test.py /tmp/ehealth2osc-bjoern-in' +new-window -n 'test-ehealth-merle' -t 'csession:27' 'python /home/sarah/dev/psychose/sensors2osc/sensors2osc/socat_ehealth_test.py /tmp/ehealth2osc-merle-in' +new-window -n 'test-ehealth-uwe' -t 'csession:28' 'python /home/sarah/dev/psychose/sensors2osc/sensors2osc/socat_ehealth_test.py /tmp/ehealth2osc-uwe-in' +new-window -n 'test-ehealth-bjoern' -t 'csession:26' 'python /home/sarah/dev/psychose/sensors2osc/sensors2osc/socat_ehealth_test.py /tmp/ehealth2osc-bjoern-in' select-window -t 'csession:2' diff --git a/dump_grabber/dump_grabber/dump_grabber.ui b/dump_grabber/dump_grabber/dump_grabber.ui index 1da8b65..c70c83a 100644 --- a/dump_grabber/dump_grabber/dump_grabber.ui +++ b/dump_grabber/dump_grabber/dump_grabber.ui @@ -10,421 +10,6 @@ 606 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - Monospace @@ -447,39 +32,428 @@ 580 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + - true + false - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 4 - - - - diff --git a/dump_grabber/dump_grabber/dump_grabber_ui.py b/dump_grabber/dump_grabber/dump_grabber_ui.py index 33517eb..de1e72a 100644 --- a/dump_grabber/dump_grabber/dump_grabber_ui.py +++ b/dump_grabber/dump_grabber/dump_grabber_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'dump_grabber.ui' # -# Created: Wed Apr 16 22:18:59 2014 +# Created: Tue May 13 06:55:09 2014 # by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -27,6 +27,19 @@ class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) MainWindow.resize(811, 606) + font = QtGui.QFont() + font.setFamily(_fromUtf8("Monospace")) + font.setPointSize(14) + font.setItalic(True) + MainWindow.setFont(font) + self.centralwidget = QtGui.QWidget(MainWindow) + self.centralwidget.setObjectName(_fromUtf8("centralwidget")) + self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.graphics_view = QtGui.QGraphicsView(self.centralwidget) + self.graphics_view.setMinimumSize(QtCore.QSize(785, 580)) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) @@ -163,23 +176,11 @@ class Ui_MainWindow(object): brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) - MainWindow.setPalette(palette) - self.centralwidget = QtGui.QWidget(MainWindow) - self.centralwidget.setObjectName(_fromUtf8("centralwidget")) - self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget) - self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.graphics_view = QtGui.QGraphicsView(self.centralwidget) - self.graphics_view.setMinimumSize(QtCore.QSize(785, 580)) - self.graphics_view.setAutoFillBackground(True) + self.graphics_view.setPalette(palette) + self.graphics_view.setAutoFillBackground(False) self.graphics_view.setObjectName(_fromUtf8("graphics_view")) self.horizontalLayout.addWidget(self.graphics_view) - spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.horizontalLayout.addItem(spacerItem) self.verticalLayout.addLayout(self.horizontalLayout) - spacerItem1 = QtGui.QSpacerItem(20, 4, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.verticalLayout.addItem(spacerItem1) MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) diff --git a/dump_grabber/dump_grabber/main.py b/dump_grabber/dump_grabber/main.py index 5c78bcb..06d0701 100644 --- a/dump_grabber/dump_grabber/main.py +++ b/dump_grabber/dump_grabber/main.py @@ -1,7 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - # This file is part of chaosc and psychosis # # chaosc is free software: you can redistribute it and/or modify @@ -21,24 +20,20 @@ from __future__ import absolute_import -import logging import os import os.path -import Queue import re -import select -import socket import sys -import threading -import time from datetime import datetime -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from chaosc.argparser_groups import * from chaosc.lib import logger, resolve_host from PyQt4 import QtCore, QtGui from PyQt4.QtCore import QBuffer, QByteArray, QIODevice +from PyQt4.QtNetwork import QTcpServer, QTcpSocket, QUdpSocket, QHostAddress + from dump_grabber.dump_grabber_ui import Ui_MainWindow +from psylib.mjpeg_streaming_server import MjpegStreamingServer try: from chaosc.c_osc_lib import OSCMessage, decode_osc @@ -68,7 +63,7 @@ class ColumnTextStorage(TextStorage): self.column_width = column_width self.line_height = line_height self.graphics_scene = scene - self.num_lines, self.offset = divmod(775, self.line_height) + self.num_lines, self.offset = divmod(768, self.line_height) def init_columns(self): for x in range(self.column_count): @@ -102,7 +97,7 @@ class ExclusiveTextStorage(TextStorage): self.column_width = column_width self.line_height = line_height self.graphics_scene = scene - self.num_lines, self.offset = divmod(775, self.line_height) + self.num_lines, self.offset = divmod(576, self.line_height) def init_columns(self): color = self.colors[0] @@ -125,10 +120,17 @@ class ExclusiveTextStorage(TextStorage): text_item.setY(iy * self.line_height) + + + class MainWindow(QtGui.QMainWindow, Ui_MainWindow): - def __init__(self, parent=None, columns=3, column_exclusive=False): + def __init__(self, args, parent=None, columns=3, column_exclusive=False): super(MainWindow, self).__init__(parent) + self.args = args + self.setupUi(self) + self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self) + self.http_server.listen(port=args.http_port) self.graphics_view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.graphics_view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.graphics_view.setRenderHint(QtGui.QPainter.Antialiasing, True) @@ -140,178 +142,78 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.default_font.setStyleHint(QtGui.QFont.Monospace) self.default_font.setBold(True) self.graphics_scene.setFont(self.default_font) - self.font_metrics = QtGui.QFontMetrics(self.default_font) self.line_height = self.font_metrics.height() self.column_width = 775 / columns self.text_storage = ExclusiveTextStorage(columns, self.default_font, self.column_width, self.line_height, self.graphics_scene) - #self.text_storage = ColumnTextStorage(columns, self.default_font, self.column_width, self.line_height, self.graphics_scene) self.text_storage.init_columns() + self.osc_sock = QUdpSocket(self) + logger.info("osc bind localhost %d", args.client_port) + self.osc_sock.bind(QHostAddress("127.0.0.1"), args.client_port) + self.osc_sock.readyRead.connect(self.got_message) + self.osc_sock.error.connect(self.handle_osc_error) + msg = OSCMessage("/subscribe") + msg.appendTypedArg("localhost", "s") + msg.appendTypedArg(args.client_port, "i") + msg.appendTypedArg(self.args.authenticate, "s") + if self.args.subscriber_label is not None: + msg.appendTypedArg(self.args.subscriber_label, "s") + self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110) + #self.add_text(0, "foo bar") + + self.regex = re.compile("^/(uwe|merle|bjoern)/(.*?)$") + + def closeEvent(self, event): + msg = OSCMessage("/unsubscribe") + msg.appendTypedArg("localhost", "s") + msg.appendTypedArg(self.args.client_port, "i") + msg.appendTypedArg(self.args.authenticate, "s") + self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110) + + def handle_osc_error(self, error): + logger.info("osc socket error %d", error) + def add_text(self, column, text): self.text_storage.add_text(column, text) - def render(self): + def render_image(self): image = QtGui.QImage(768, 576, QtGui.QImage.Format_ARGB32_Premultiplied) image.fill(QtCore.Qt.black) painter = QtGui.QPainter(image) - painter.setRenderHints(QtGui.QPainter.RenderHint(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing), True) + painter.setRenderHints(QtGui.QPainter.RenderHint( + QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing), + True) painter.setFont(self.default_font) - self.graphics_view.render(painter, target=QtCore.QRectF(0,0,768,576),source=QtCore.QRect(0,0,768,576)) + self.graphics_view.render(painter, target=QtCore.QRectF(0, 0, 768, 576), + source=QtCore.QRect(0, 0, 768, 576)) painter.end() - return image + buf = QBuffer() + buf.open(QIODevice.WriteOnly) + image.save(buf, "JPG", 80) + image_data = buf.data() + return image_data - -class OSCThread(threading.Thread): - def __init__(self, args): - super(OSCThread, self).__init__() - self.args = args - self.running = True - - self.client_address = resolve_host(args.client_host, args.client_port, args.address_family) - - self.chaosc_address = chaosc_host, chaosc_port = resolve_host(args.chaosc_host, args.chaosc_port, args.address_family) - - self.osc_sock = socket.socket(args.address_family, 2, 17) - self.osc_sock.bind(self.client_address) - self.osc_sock.setblocking(0) - - logger.info("starting up osc receiver on '%s:%d'", self.client_address[0], self.client_address[1]) - - self.subscribe_me() - - def subscribe_me(self): - logger.info("%s: subscribing to '%s:%d' with label %r", datetime.now().strftime("%x %X"), self.chaosc_address[0], self.chaosc_address[1], self.args.subscriber_label) - msg = OSCMessage("/subscribe") - msg.appendTypedArg(self.client_address[0], "s") - msg.appendTypedArg(self.client_address[1], "i") - msg.appendTypedArg(self.args.authenticate, "s") - if self.args.subscriber_label is not None: - msg.appendTypedArg(self.args.subscriber_label, "s") - self.osc_sock.sendto(msg.encode_osc(), self.chaosc_address) - - - def unsubscribe_me(self): - if self.args.keep_subscribed: - return - - logger.info("unsubscribing from '%s:%d'", self.chaosc_address[0], self.chaosc_address[1]) - msg = OSCMessage("/unsubscribe") - msg.appendTypedArg(self.client_address[0], "s") - msg.appendTypedArg(self.client_address[1], "i") - msg.appendTypedArg(self.args.authenticate, "s") - self.osc_sock.sendto(msg.encode_osc(), self.chaosc_address) - - def run(self): - - while self.running: + def got_message(self): + while self.osc_sock.hasPendingDatagrams(): + data, address, port = self.osc_sock.readDatagram(self.osc_sock.pendingDatagramSize()) try: - reads, writes, errs = select.select([self.osc_sock], [], [], 0.01) - except Exception, e: + osc_address, typetags, args = decode_osc(data, 0, len(data)) + except Exception: + return + try: + actor, text = self.regex.match(osc_address).groups() + except AttributeError: pass else: - if reads: - try: - osc_input, address = self.osc_sock.recvfrom(8192) - osc_address, typetags, messages = decode_osc(osc_input, 0, len(osc_input)) - queue.put_nowait((osc_address, messages)) - except Exception, e: - pass - else: - pass - - self.unsubscribe_me() - logger.info("OSCThread is going down") - - -queue = Queue.Queue() - -class MyHandler(BaseHTTPRequestHandler): - - def do_GET(self): - - try: - self.path=re.sub('[^.a-zA-Z0-9]', "",str(self.path)) - if self.path=="" or self.path==None or self.path[:1]==".": - self.send_error(403,'Forbidden') - - if self.path.endswith(".html"): - directory = os.path.dirname(os.path.abspath(__file__)) - data = open(os.path.join(directory, self.path), "rb").read() - self.send_response(200) - self.send_header('Content-type', 'text/html') - self.end_headers() - self.wfile.write(data) - elif self.path.endswith(".mjpeg"): - self.thread = thread = OSCThread(self.server.args) - thread.daemon = True - thread.start() - window = MainWindow() - window.hide() - - self.send_response(200) - self.send_header("Content-Type", "multipart/x-mixed-replace; boundary=--aaboundary") - self.end_headers() - - event_loop = QtCore.QEventLoop() - last_frame = time.time() - 1. - frame_rate = 16.0 - frame_length = 1. / frame_rate - regex = re.compile("^/(uwe|merle|bjoern)/(.*?)$") - while 1: - event_loop.processEvents() - app.sendPostedEvents(None, 0) - while 1: - try: - osc_address, args = queue.get_nowait() - print osc_address - except Queue.Empty: - break - else: - try: - actor, text = regex.match(osc_address).groups() - if actor == "merle": - window.add_text(0, "%s = %s" % (text, ", ".join([str(i) for i in args]))) - if actor == "uwe": - window.add_text(1, "%s = %s" % (text, ", ".join([str(i) for i in args]))) - if actor == "bjoern": - window.add_text(2, "%s = %s" % (text, ", ".join([str(i) for i in args]))) - except AttributeError: - pass - - now = time.time() - delta = now - last_frame - if delta > frame_length: - last_frame = now - img = window.render() - buffer = QBuffer() - buffer.open(QIODevice.WriteOnly) - img.save(buffer, "JPG") - JpegData = buffer.data() - self.wfile.write("--aaboundary\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len(JpegData), JpegData)) - JpegData = None - buffer = None - img = None - time.sleep(0.01) - return - except (KeyboardInterrupt, SystemError): - if hasattr(self, "thread") and self.thread is not None: - self.thread.running = False - self.thread.join() - self.thread = None - except IOError, e: - if e[0] in (32, 104): - if hasattr(self, "thread") and self.thread is not None: - self.thread.running = False - self.thread.join() - self.thread = None - else: - pass - - -class JustAHTTPServer(HTTPServer): - pass + if actor == "merle": + self.add_text(0, "%s = %s" % (text, ", ".join([str(i) for i in args]))) + elif actor == "uwe": + self.add_text(1, "%s = %s" % (text, ", ".join([str(i) for i in args]))) + elif actor == "bjoern": + self.add_text(2, "%s = %s" % (text, ", ".join([str(i) for i in args]))) + return True def main(): @@ -327,13 +229,10 @@ def main(): args = arg_parser.finalize() http_host, http_port = resolve_host(args.http_host, args.http_port, args.address_family) + window = MainWindow(args) + #window.show() + app.exec_() - server = JustAHTTPServer((http_host, http_port), MyHandler) - server.address_family = args.address_family - server.args = args - logger.info("starting up http server on '%s:%d'", http_host, http_port) - - server.serve_forever() if ( __name__ == '__main__' ): main() diff --git a/ekgplotter/ekgplotter/main_qt.py b/ekgplotter/ekgplotter/main_qt.py new file mode 100644 index 0000000..bec7536 --- /dev/null +++ b/ekgplotter/ekgplotter/main_qt.py @@ -0,0 +1,312 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# This file is part of sensors2osc package +# +# sensors2osc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# sensors2osc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with sensors2osc. If not, see . +# +# found the mjpeg part here, thanks for the nice code :) +# http://hardsoftlucid.wordpress.com/2013/04/11/mjpeg-server-for-webcam-in-python-with-opencv/ +# the osc integration stuff is implemented by me +# +# Copyright (C) 2014 Stefan Kögl + +from __future__ import absolute_import + +from chaosc.argparser_groups import * +from chaosc.lib import logger, resolve_host +from datetime import datetime +from operator import attrgetter +from PyQt4 import QtGui, QtCore +from PyQt4.QtCore import QBuffer, QByteArray, QIODevice +from PyQt4.QtNetwork import QTcpServer, QTcpSocket, QUdpSocket, QHostAddress +from PyQt4.QtGui import QPixmap + +import logging +import numpy as np +import os.path +import pyqtgraph as pg +from pyqtgraph.widgets.PlotWidget import PlotWidget +import Queue +import re +import select +import socket +import threading +import time + +from psylib.mjpeg_streaming_server import MjpegStreamingServer + + +try: + from chaosc.c_osc_lib import OSCMessage, decode_osc +except ImportError as e: + logging.exception(e) + from chaosc.osc_lib import OSCMessage, decode_osc + + +def get_steps(pulse_rate, rate): + beat_length = 60. / pulse_rate + steps_pre = int(beat_length / rate) + 1 + used_sleep_time = beat_length / steps_pre + steps = int(beat_length / used_sleep_time) + return steps, used_sleep_time + + +class Generator(object): + def __init__(self, pulse=92, delta=0.08): + self.count = 0 + self.pulse = 92 + self.delta = delta + self.steps = get_steps(self.pulse, delta / 2) + + def __call__(self): + while 1: + value = random.randint(0, steps) + if self.count < int(steps / 100. * 20): + value = random.randint(0,20) + elif self.count < int(steps / 100. * 30): + value = random.randint(20, 30) + elif self.count < int(steps / 100. * 40): + value = random.randint(30,100) + elif self.count < int(steps / 2.): + value = random.randint(100,200) + elif self.count == int(steps / 2.): + value = 255 + elif self.count < int(steps / 100. * 60): + value = random.randint(100, 200) + elif self.count < int(steps / 100. * 70): + value = random.randint(50, 100) + elif self.count < int(steps / 100. * 80): + value = random.randint(20, 50) + elif self.count <= steps: + value = random.randint(0,20) + elif self.count >= steps: + self.count = 0 + + self.count += 1 + yield value + + def retrigger(self): + self.count = self.steps / 2 + + +class Actor(object): + def __init__(self, name, num_data, color, ix, max_actors, actor_height): + self.name = name + self.num_data = num_data + self.color = color + self.ix = ix + self.max_actors = max_actors + self.actor_height = actor_height + self.updated = 0 + + self.offset = ix * actor_height + self.data = np.array([self.offset] * num_data) + self.head = 0 + self.pre_head = 0 + self.plotItem = pg.PlotCurveItem(pen=pg.mkPen(color, width=3), name=name) + self.plotPoint = pg.ScatterPlotItem(pen=pg.mkPen("w", width=5), brush=pg.mkBrush(color), size=5) + + def __str__(self): + return "" % (self.name, self.active, self.head) + + __repr__ = __str__ + + + def add_value(self, value): + dp = self.head + self.data[dp] = value / self.max_actors + self.offset + self.pre_head = dp + self.head = (dp + 1) % self.num_data + self.updated += 1 + + def fill_missing(self, count): + dp = self.head + for i in range(count): + self.data[dp] = self.offset + dp = (dp + 1) % self.num_data + self.updated += 1 + + self.pre_head = (dp - 1) % self.num_data + self.head = dp + + def render(self): + self.plotItem.setData(y=self.data, clear=True) + self.plotPoint.setData(x=[self.pre_head], y = [self.data[self.pre_head]]) + + +class EkgPlotWidget(PlotWidget): + def __init__(self, args, parent=None): + super(EkgPlotWidget, self).__init__(parent) + self.args = args + + self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self) + self.http_server.listen(port=args.http_port) + + self.osc_sock = QUdpSocket(self) + logger.info("osc bind localhost %d", args.client_port) + self.osc_sock.bind(QHostAddress("127.0.0.1"), args.client_port) + self.osc_sock.readyRead.connect(self.got_message) + self.osc_sock.error.connect(self.handle_osc_error) + msg = OSCMessage("/subscribe") + msg.appendTypedArg("localhost", "s") + msg.appendTypedArg(args.client_port, "i") + msg.appendTypedArg(self.args.authenticate, "s") + if self.args.subscriber_label is not None: + msg.appendTypedArg(self.args.subscriber_label, "s") + self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110) + self.num_data = 100 + + self.hide() + self.showGrid(False, False) + self.setYRange(0, 255) + self.setXRange(0, self.num_data) + self.resize(768, 576) + + + colors = ["r", "g", "b"] + + ba = self.getAxis("bottom") + bl = self.getAxis("left") + ba.setTicks([]) + bl.setTicks([]) + ba.hide() + bl.hide() + self.active_actors = list() + + self.actors = dict() + self.lengths1 = [0] + + self.max_value = 255 + actor_names = ["merle", "uwe", "bjoern" ] + self.max_actors = len(actor_names) + self.actor_height = self.max_value / self.max_actors + + for ix, (actor_name, color) in enumerate(zip(actor_names, colors)): + self.add_actor(actor_name, self.num_data, color, ix, self.max_actors, self.actor_height) + + self.set_positions() + + self.ekg_regex = re.compile("^/(.*?)/ekg$") + self.ctl_regex = re.compile("^/plot/(.*?)$") + self.updated_actors = set() + self.new_round() + + + def add_actor(self, actor_name, num_data, color, ix, max_actors, actor_height): + actor_obj = Actor(actor_name, num_data, color, ix, max_actors, actor_height) + self.actors[actor_name] = actor_obj + self.addItem(actor_obj.plotItem) + self.addItem(actor_obj.plotPoint) + self.active_actors.append(actor_obj) + + + def set_positions(self): + for ix, actor_obj in enumerate(self.active_actors): + actor_obj.plotItem.setPos(0, ix * 2) + actor_obj.plotPoint.setPos(0, ix * 2) + + def active_actor_count(self): + return self.max_actors + + def new_round(self): + for ix, actor in enumerate(self.active_actors): + actor.updated = 0 + + def update_missing_actors(self): + liste = sorted(self.active_actors, key=attrgetter("updated")) + max_values = liste[-1].updated + if max_values == 0: + # handling no signal + for actor in self.active_actors: + actor.add_value(0) + return + for ix, actor in enumerate(self.active_actors): + diff = max_values - actor.updated + if diff > 0: + for i in range(diff): + actor.add_value(0) + + + def update(self, osc_address, value): + + res = self.ekg_regex.match(osc_address) + if res: + actor_name = res.group(1) + actor_obj = self.actors[actor_name] + actor_obj.add_value(value) + + + def render(self): + for ix, actor in enumerate(self.active_actors): + actor.render() + + def closeEvent(self, event): + msg = OSCMessage("/unsubscribe") + msg.appendTypedArg("localhost", "s") + msg.appendTypedArg(self.args.client_port, "i") + msg.appendTypedArg(self.args.authenticate, "s") + self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110) + + def handle_osc_error(self, error): + logger.info("osc socket error %d", error) + + def render_image(self): + self.update_missing_actors() + self.render() + exporter = pg.exporters.ImageExporter.ImageExporter(self.plotItem) + exporter.parameters()['width'] = 768 + img = exporter.export(toBytes=True) + buf = QBuffer() + buf.open(QIODevice.WriteOnly) + img.save(buf, "JPG", 75) + JpegData = buf.data() + self.new_round() + return JpegData + + def got_message(self): + while self.osc_sock.hasPendingDatagrams(): + data, address, port = self.osc_sock.readDatagram(self.osc_sock.pendingDatagramSize()) + try: + osc_address, typetags, args = decode_osc(data, 0, len(data)) + except Exception, e: + logger.exception(e) + return + else: + self.update(osc_address, args[0]) + return True + + + +def main(): + arg_parser = ArgParser("ekgplotter") + arg_parser.add_global_group() + client_group = arg_parser.add_client_group() + arg_parser.add_argument(client_group, '-x', "--http_host", default="::", + help='my host, defaults to "::"') + arg_parser.add_argument(client_group, '-X', "--http_port", default=9000, + type=int, help='my port, defaults to 9000') + arg_parser.add_chaosc_group() + arg_parser.add_subscriber_group() + args = arg_parser.finalize() + + args.http_host, args.http_port = resolve_host(args.http_host, args.http_port, args.address_family) + + qtapp = QtGui.QApplication([]) + widget = EkgPlotWidget(args) + qtapp.exec_() + + +if __name__ == '__main__': + main() diff --git a/ekgplotter/setup.py b/ekgplotter/setup.py index 3cb5b9a..85ac4de 100644 --- a/ekgplotter/setup.py +++ b/ekgplotter/setup.py @@ -30,7 +30,7 @@ setup( # predefined extension points, e.g. for plugins entry_points = """ [console_scripts] - ekgplotter = ekgplotter.main:main + ekgplotter = ekgplotter.main_qt:main """, # pypi metadata author = "Stefan Kögl", diff --git a/sensors2osc/sensors2osc/common.py b/sensors2osc/sensors2osc/common.py index d63531f..6779cab 100644 --- a/sensors2osc/sensors2osc/common.py +++ b/sensors2osc/sensors2osc/common.py @@ -28,6 +28,7 @@ import time import sys from chaosc.argparser_groups import ArgParser +from chaosc.lib import logger try: @@ -47,8 +48,8 @@ class Platform(object): def connect(self): - print "connect serial" - print "waiting for the device %r to come up" % self.args.device + logger.info("connect serial") + logger.info("waiting for the device %r to come up", self.args.device) self.serial_sock = serial.Serial() self.serial_sock.port = self.args.device self.serial_sock.baudrate = 115200 @@ -57,7 +58,7 @@ class Platform(object): try: self.serial_sock.open() except (serial.serialutil.SerialException, os.error), e: - print "serial error", e + logger.exception(e) time.sleep(0.5) pass else: @@ -66,12 +67,12 @@ class Platform(object): def close(self): if self.serial_sock is not None: - print "close serial" + logger.info("close serial") self.serial_sock.close() def reconnect(self): - print "reconnect serial" + logger.info("reconnect serial") self.close() self.connect() diff --git a/sensors2osc/sensors2osc/ehealth2osc.py b/sensors2osc/sensors2osc/ehealth2osc.py index df7819b..e65fc54 100644 --- a/sensors2osc/sensors2osc/ehealth2osc.py +++ b/sensors2osc/sensors2osc/ehealth2osc.py @@ -39,20 +39,19 @@ def main(): #print repr(data) except (socket.error, serial.serialutil.SerialException), msg: # got disconnected? - print "serial socket error!!!", msg + logger.exception(msg) platform.reconnect() - print "data", repr(data) try: airFlow, emg, temp = data.split(";") - except ValueError, e: - print e + except ValueError, msg: + logger.exception(msg) continue try: airFlow = int(airFlow) - except ValueError, e: - print e + except ValueError, msg: + logger.exception(msg) continue try: @@ -60,14 +59,14 @@ def main(): osc_message.appendTypedArg(airFlow, "i") platform.osc_sock.sendto(osc_message.encode_osc(), platform.remote) except socket.error, msg: - print "cannot connect to chaosc", msg + logger.exception(msg) continue try: emg = int(emg) - except ValueError, e: - print e + except ValueError, msg: + logger.exception(msg) continue try: @@ -75,14 +74,14 @@ def main(): osc_message.appendTypedArg(emg, "i") platform.osc_sock.sendto(osc_message.encode_osc(), platform.remote) except socket.error, msg: - print "cannot connect to chaosc", msg + logger.exception(msg) continue try: temp = int(temp) - except ValueError, e: - print e + except ValueError, msg: + logger.exception(msg) continue try: @@ -90,7 +89,7 @@ def main(): osc_message.appendTypedArg(temp, "i") platform.osc_sock.sendto(osc_message.encode_osc(), platform.remote) except socket.error, msg: - print "cannot connect to chaosc", msg + logger.exception(msg) continue diff --git a/sensors2osc/sensors2osc/ekg2osc.py b/sensors2osc/sensors2osc/ekg2osc.py index 2517af5..fcee877 100644 --- a/sensors2osc/sensors2osc/ekg2osc.py +++ b/sensors2osc/sensors2osc/ekg2osc.py @@ -51,11 +51,11 @@ def main(): except TypeError, e: continue - if msg_count >= 20: - logger.info("value = %d", t) - msg_count = 0 - else: - msg_count += 1 + #if msg_count >= 20: + # logger.info("value = %d", t) + # msg_count = 0 + #else: + # msg_count += 1 try: osc_message = OSCMessage("/%s/ekg" % actor) diff --git a/sensors2osc/sensors2osc/main.py b/sensors2osc/sensors2osc/main.py index fa5545d..01a0606 100644 --- a/sensors2osc/sensors2osc/main.py +++ b/sensors2osc/sensors2osc/main.py @@ -29,7 +29,6 @@ import datetime try: from chaosc.c_osc_lib import OSCMessage except ImportError as e: - print(e) from chaosc.osc_lib import OSCMessage @@ -48,7 +47,7 @@ class Forwarder(object): def close(self): """Close all resources and unpublish service""" - print "%s: closing..." % (self.device, ) + logger.info("%s: closing...", self.device) self.serial.close() @@ -58,7 +57,6 @@ class EHealth2OSC(Forwarder): def handle_read(self, osc_sock): data = self.serial.readline()[:-2] - print repr(data) try: airFlow, emg, temp = data.split(";") except ValueError: @@ -106,7 +104,7 @@ class RingBuffer(object): self.head = (self.head + 1) % self.length def getData(self): - print "getData", self.ring_buf, self.head + #print "getData", self.ring_buf, self.head data = list() for i in range(7, 1, -1): value = self.ring_buf[(self.head - i) % self.length] @@ -146,7 +144,7 @@ class Pulse2OSC(Forwarder): osc_message.appendTypedArg(heart_rate, "i") osc_message.appendTypedArg(o2, "i") osc_sock.sendall(osc_message.encode_osc()) - print "heartbeat", datetime.datetime.now(), heart_signal + #print "heartbeat", datetime.datetime.now(), heart_signal self.heartbeat_on = True elif pulse == 1 and self.heartbeat_on: #print "off heartbeat", datetime.datetime.now(), heart_signal diff --git a/sensors2osc/sensors2osc/pulse2osc.py b/sensors2osc/sensors2osc/pulse2osc.py index c0b8ad6..0746acc 100644 --- a/sensors2osc/sensors2osc/pulse2osc.py +++ b/sensors2osc/sensors2osc/pulse2osc.py @@ -41,7 +41,7 @@ class RingBuffer(object): self.head = (self.head + 1) % self.length def getData(self): - print "getData", self.ring_buf, self.head + #print "getData", self.ring_buf, self.head data = list() for i in range(self.length + 1, 1, -1): value = self.ring_buf[(self.head - i) % self.length] @@ -52,7 +52,7 @@ class RingBuffer(object): raise ValueError("not complete - reset ringbuffer") data.append(value) if data[0] != 0x0 or data[1] != 0xff: - print "issue", data + #print "issue", data self.reset() self.ring_buf[0] = 0 self.head = 1 @@ -77,7 +77,7 @@ def main(): continue except (socket.error, serial.serialutil.SerialException), msg: # got disconnected? - print "serial socket error!!!", msg + logger.exception(msg) platform.reconnect() try: @@ -91,8 +91,8 @@ def main(): if t == 0: try: heart_signal, heart_rate, o2, pulse = buf.getData() - except ValueError, e: - print e + except ValueError, msg: + logger.exception(msg) continue if pulse == 245 and not heartbeat_on: @@ -103,12 +103,12 @@ def main(): osc_message.appendTypedArg(heart_rate, "i") osc_message.appendTypedArg(o2, "i") platform.osc_sock.sendto(osc_message.encode_osc(), platform.remote) - print "on heartbeat", datetime.now(), heart_signal, heart_rate, o2, pulse + #print "on heartbeat", datetime.now(), heart_signal, heart_rate, o2, pulse except socket.error, msg: - print "cannot connect to chaosc" + logger.exception(msg) continue elif pulse == 1 and heartbeat_on: - print "off heartbeat", datetime.now(), heart_signal, heart_rate, o2, pulse + #print "off heartbeat", datetime.now(), heart_signal, heart_rate, o2, pulse heartbeat_on = False try: osc_message = OSCMessage("/%s/heartbeat" % actor) @@ -117,7 +117,7 @@ def main(): osc_message.appendTypedArg(o2, "i") platform.osc_sock.sendto(osc_message.encode_osc(), platform.remote) except socket.error, msg: - print "cannot connect to chaosc" + logger.exception(msg) continue diff --git a/texter/texter/448_texter.db b/texter/texter/448_texter.db deleted file mode 100644 index 2604dd1d42f9aa2b580e05baec4f45c61e5e765f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5412 zcmeHLL2uJA6z)1;tQ+hCcM}dng4EsC14`2#=q7=*!8STk4rp>yH?fw)j^j2f6A~B7 zzu^~h;5l7NJ5|$!rfn*%qAGTr=X}rK^LwAa=f6Y?FPcwsxg7BodWj-bMUGF2L_X_L zCuX8>+zkbDPJDW-`hpgs{L$)u|3Hu`*6XQ=?b&R(g+rtOtVhQ7&_XaXNrG$lXk|f1stH`B`8;AICr*3*G z@XI9}gK z{`RV&2Vf_T!H{?b5YZV*Jf^4_3*t}<2}#XF5>Y|l3N}m(Z`N!)oUFy$eUx@p4b*`- z%nk{d-?!ES9`e+{dS&<^W?!kbxhd3nJE6?=lvOU3vH{w%6>=F6zoJozk>!9$mxZR{ z!YYZ=3OaAMxKccr=<;btO?1d~mh>$%QfABN&WXvwHaNqAnKk%P6HFW68$rU+%oCXQ zrek`oF*)4l(t6(Ml;8(urcnpH2e!d^Vm8nv5EwD((KIfmgVR12L=qPWu1I9WV9hcZ1xyUru#k%lbdnzb%srx5$jTXb8hKv?V5KiNmW;d;D6C= z`7HKkjaoWE06L$Ow$5xl#=CKv1!fIsn)ouLA*8uWP|hRiS|bUHGZ*T{Y!{myW}O4$ zKr1e2h-jNBE@uwk2H=YokqW9^uB%W-cZQdj_r^rZ?}fQY(dy-2L<;el8&(Ym6a|&WNIBfHW*H*U(gd_fw&-I7zQB?t z&60CEE5%1S)6(tqh{o^x#C%Sl1ibUx#x - - - - 768 - 576 - - - - - 16777215 - 576 - - - - Qt::Horizontal - - - - - 1 - 0 - - - - - 200 - 576 - - - - - 16777215 - 576 - - - - - - - 100 - 0 - - - - - 0 - 576 - - - - - 768 - 576 - - - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - true - - - + + + + + + + + + 100 + 0 + + + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + false + + + true + + + Qt::NoTextInteraction + + + + @@ -526,11 +490,6 @@ QPushButton
karrowbutton.h
- - KRichTextEdit - KTextEdit -
krichtextedit.h
-
KButtonGroup QGroupBox @@ -542,16 +501,6 @@ QPushButton
kpushbutton.h
- - KTextEdit - QTextEdit -
ktextedit.h
-
- - KRichTextWidget - KRichTextEdit -
krichtextwidget.h
-
diff --git a/texter/texter/text_sorter_ui.py b/texter/texter/edit_dialog_ui.py similarity index 85% rename from texter/texter/text_sorter_ui.py rename to texter/texter/edit_dialog_ui.py index 1a0f767..9dfbd4f 100644 --- a/texter/texter/text_sorter_ui.py +++ b/texter/texter/edit_dialog_ui.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'texter4.ui' +# Form implementation generated from reading ui file 'edit_dialog.ui' # -# Created: Mon Apr 28 21:58:51 2014 +# Created: Sat May 17 16:15:38 2014 # by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -23,32 +23,23 @@ except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) -class Ui_TextSorterDialog(object): - def setupUi(self, TextSorterDialog): - TextSorterDialog.setObjectName(_fromUtf8("TextSorterDialog")) - TextSorterDialog.resize(1084, 633) - self.verticalLayout = QtGui.QVBoxLayout(TextSorterDialog) +class Ui_EditDialog(object): + def setupUi(self, EditDialog): + EditDialog.setObjectName(_fromUtf8("EditDialog")) + EditDialog.resize(1084, 633) + self.verticalLayout = QtGui.QVBoxLayout(EditDialog) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) - self.splitter = QtGui.QSplitter(TextSorterDialog) - self.splitter.setOrientation(QtCore.Qt.Horizontal) - self.splitter.setObjectName(_fromUtf8("splitter")) - self.text_list = QtGui.QListView(self.splitter) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(1) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.text_list.sizePolicy().hasHeightForWidth()) - self.text_list.setSizePolicy(sizePolicy) - self.text_list.setMinimumSize(QtCore.QSize(200, 576)) - self.text_list.setMaximumSize(QtCore.QSize(16777215, 576)) + self.horizontalLayout_2 = QtGui.QHBoxLayout() + self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) + self.text_list = QtGui.QTableView(EditDialog) self.text_list.setObjectName(_fromUtf8("text_list")) - self.text_preview = KRichTextWidget(self.splitter) + self.horizontalLayout_2.addWidget(self.text_list) + self.text_preview = QtGui.QTextEdit(EditDialog) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(100) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.text_preview.sizePolicy().hasHeightForWidth()) self.text_preview.setSizePolicy(sizePolicy) - self.text_preview.setMinimumSize(QtCore.QSize(0, 576)) - self.text_preview.setMaximumSize(QtCore.QSize(768, 576)) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) @@ -186,10 +177,13 @@ class Ui_TextSorterDialog(object): brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) self.text_preview.setPalette(palette) + self.text_preview.setUndoRedoEnabled(False) self.text_preview.setReadOnly(True) + self.text_preview.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) self.text_preview.setObjectName(_fromUtf8("text_preview")) - self.verticalLayout.addWidget(self.splitter) - self.kbuttongroup = KButtonGroup(TextSorterDialog) + self.horizontalLayout_2.addWidget(self.text_preview) + self.verticalLayout.addLayout(self.horizontalLayout_2) + self.kbuttongroup = KButtonGroup(EditDialog) self.kbuttongroup.setObjectName(_fromUtf8("kbuttongroup")) self.horizontalLayout = QtGui.QHBoxLayout(self.kbuttongroup) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) @@ -206,14 +200,12 @@ class Ui_TextSorterDialog(object): self.remove_button.setObjectName(_fromUtf8("remove_button")) self.horizontalLayout.addWidget(self.remove_button) self.verticalLayout.addWidget(self.kbuttongroup) - spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.verticalLayout.addItem(spacerItem) - self.retranslateUi(TextSorterDialog) - QtCore.QMetaObject.connectSlotsByName(TextSorterDialog) + self.retranslateUi(EditDialog) + QtCore.QMetaObject.connectSlotsByName(EditDialog) - def retranslateUi(self, TextSorterDialog): - TextSorterDialog.setWindowTitle(_translate("TextSorterDialog", "Form", None)) - self.remove_button.setText(_translate("TextSorterDialog", "Remove", None)) + def retranslateUi(self, EditDialog): + EditDialog.setWindowTitle(_translate("EditDialog", "Form", None)) + self.remove_button.setText(_translate("EditDialog", "Remove", None)) -from PyKDE4.kdeui import KButtonGroup, KArrowButton, KPushButton, KRichTextWidget +from PyKDE4.kdeui import KButtonGroup, KArrowButton, KPushButton diff --git a/texter/texter/favicon.ico b/texter/texter/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..791d34c458a4620515f3064f27dc2ad55c3ac1b1 GIT binary patch literal 766 zcmZQzU<5)11py$*!tjELfkBLcfk6X^6@b_Qh(Y3`U^E115d!~706)KfKK%Rn@7)*w mU!1%8|LWa0{|it2{r~s-lm9Q@eEt9R?vMXJgZU&F`5yp2{(IE` literal 0 HcmV?d00001 diff --git a/texter/texter/favicon.pnm b/texter/texter/favicon.pnm new file mode 100644 index 0000000..1faed8a --- /dev/null +++ b/texter/texter/favicon.pnm @@ -0,0 +1,5 @@ +P6 +# CREATOR: GIMP PNM Filter Version 1.1 +16 16 +255 +yyyeeetttQQQlllbbb󨨨^^^ppp666ח~~~bbbЭ666hhhʔ \ No newline at end of file diff --git a/texter/texter/main.py b/texter/texter/main.py index 38e4107..de4d46a 100644 --- a/texter/texter/main.py +++ b/texter/texter/main.py @@ -60,7 +60,8 @@ class MjpegStreamingServer(QTcpServer): super(MjpegStreamingServer, self).__init__(parent) self.server_address = server_address self.newConnection.connect(self.start_streaming) - self.widget = parent + self.widget = parent.live_text + self.win_id = self.widget.winId() self.sockets = list() self.img_data = None self.timer = QtCore.QTimer() @@ -72,6 +73,7 @@ class MjpegStreamingServer(QTcpServer): self.coords = parent.live_text_rect() def handle_request(self): + print "foo" sock = self.sender() logger.info("handle_request: %s %d", sock.peerAddress(), sock.peerPort()) sock_id = id(sock) @@ -89,7 +91,7 @@ class MjpegStreamingServer(QTcpServer): resource, ext, http_version = self.regex.match(line).groups() logger.info("resource=%r, ext=%r, http_version=%r", resource, ext, http_version) except AttributeError: - loggging.info("no matching request - sending 404 not found") + logger.info("no matching request - sending 404 not found") sock.write("HTTP/1.1 404 Not Found\r\n") else: if ext == "ico": @@ -159,10 +161,11 @@ class MjpegStreamingServer(QTcpServer): if not self.stream_clients: return - pixmap = QPixmap.grabWidget(self.widget.live_text, *self.coords) + #pixmap = QPixmap.grabWidget(self.widget, QtCore.QRect(10, 10, 768, 576)) + pixmap = QPixmap.grabWindow(self.win_id, 5, 5, 768, 576) buf = QBuffer() buf.open(QIODevice.WriteOnly) - pixmap.save(buf, "JPG", 25) + pixmap.save(buf, "JPG", 30) self.img_data = buf.data() len_data = len(self.img_data) array = QByteArray("--2342\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len_data, self.img_data)) @@ -368,7 +371,7 @@ class MainWindow(KMainWindow, Ui_MainWindow): super(MainWindow, self).__init__(parent) self.args = args self.is_streaming = False - self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self) + self.live_center_action = None self.preview_center_action = None self.live_size_action = None @@ -392,6 +395,10 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.is_auto_publish = False self.setupUi(self) + self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self) + + self.live_text.setLineWrapMode(QtGui.QTextEdit.LineWrapMode(QtGui.QTextEdit.FixedPixelWidth)) + self.live_text.setLineWrapColumnOrWidth(768) self.font = QtGui.QFont("monospace", self.default_size) self.font.setStyleHint(QtGui.QFont.TypeWriter) @@ -431,6 +438,12 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.show() + def getPreviewCoords(self): + public_rect = self.preview_text.geometry() + global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight())) + return global_rect.x(), global_rect.y() + + def filter_editor_actions(self): disabled_action_names = [ "action_to_plain_text", @@ -620,14 +633,14 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.dialog.exec_() def live_text_rect(self): - return 3, 3, 768, 576 + return 5, 5, 768, 576 def stop_streaming(self): self.is_streaming = False self.http_server.stop() def start_streaming(self): - self.http_server.listen(port=9009) + self.http_server.listen(port=self.args.http_port) self.is_streaming = True def focusChanged(self, old, new): @@ -814,6 +827,8 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.dialog.setButtons(KDialog.Close) self.dialog_widget = EditDialog(self.dialog) self.dialog.setMainWidget(self.dialog_widget) + pos_x, pos_y = self.getPreviewCoords() + self.dialog.move(pos_x, self.pos().y()) self.dialog.exec_() self.fill_combo_box() diff --git a/texter/texter/text_model.py b/texter/texter/text_model.py index 558ef45..e99e2dc 100644 --- a/texter/texter/text_model.py +++ b/texter/texter/text_model.py @@ -16,7 +16,7 @@ class TextModel(QtCore.QAbstractTableModel): return len(self.text_db) def columnCount(self, parent=QtCore.QModelIndex()): - return 2 + return 1 def data(self, index, role): if not index.isValid() or \ @@ -26,12 +26,9 @@ class TextModel(QtCore.QAbstractTableModel): row = index.row() column = index.column() if role == QtCore.Qt.DisplayRole: - return self.text_db[row][column] + print "data", row, column, row + return QtCore.QVariant(self.text_db[row][column]) #return "foo bar" - elif role == QtCore.Qt.ForegroundRole: - return QtGui.QBrush(QtCore.Qt.black) - elif role == QtCore.Qt.BackgroundRole: - return QtGui.QBrush(QtCore.Qt.white) return QtCore.QVariant() @@ -45,6 +42,12 @@ class TextModel(QtCore.QAbstractTableModel): return QtCore.QVariant() def setData(self, index, value, role): + if (not index.isValid() or + not 0 <= index.row() < self.rowCount()): + print "setData index not valid" + return False + + print "setData", index.row(), index.column(), value, role if role == QtCore.Qt.EditRole: text = value.toString() @@ -52,14 +55,16 @@ class TextModel(QtCore.QAbstractTableModel): return False else: self.text_db[index.row()][index.column()] = text - - return True + return True + else: + return False def flags(self, index): - return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled + if index.column() == 0: + return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled + else: + return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - def supportedDropActions(self): - return QtCore.Qt.MoveAction def insertRows(self, row, count, parent=QtCore.QModelIndex()): self.beginInsertRows(parent, row, row+count+1) diff --git a/texter/texter/texter.html b/texter/texter/texter.html new file mode 120000 index 0000000..64233a9 --- /dev/null +++ b/texter/texter/texter.html @@ -0,0 +1 @@ +index.html \ No newline at end of file diff --git a/texter/texter/texter_ui.py b/texter/texter/texter_ui.py index 66a337d..cdc27fc 100644 --- a/texter/texter/texter_ui.py +++ b/texter/texter/texter_ui.py @@ -62,8 +62,8 @@ class Ui_MainWindow(object): self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.live_text = KRichTextWidget(self.centralwidget) - self.live_text.setMinimumSize(QtCore.QSize(775, 582)) - self.live_text.setMaximumSize(QtCore.QSize(775, 582)) + self.live_text.setMinimumSize(QtCore.QSize(778, 586)) + self.live_text.setMaximumSize(QtCore.QSize(778, 586)) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) @@ -116,8 +116,8 @@ class Ui_MainWindow(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.preview_text.sizePolicy().hasHeightForWidth()) self.preview_text.setSizePolicy(sizePolicy) - self.preview_text.setMinimumSize(QtCore.QSize(300, 582)) - self.preview_text.setMaximumSize(QtCore.QSize(775, 582)) + self.preview_text.setMinimumSize(QtCore.QSize(300, 586)) + self.preview_text.setMaximumSize(QtCore.QSize(778, 586)) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) From 05745e3c52819f4638d6f38ce9b38735529d41f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Sun, 25 May 2014 15:53:50 +0200 Subject: [PATCH 4/7] consolidate mjpeg streaming server using qtnetwork into a reusable class --- psylib/psylib/__init__.py | 0 psylib/psylib/mjpeg_streaming_server.py | 169 ++++++++++++++++++++++++ psylib/psylib/other.py | 164 +++++++++++++++++++++++ psylib/setup.py | 43 ++++++ 4 files changed, 376 insertions(+) create mode 100644 psylib/psylib/__init__.py create mode 100644 psylib/psylib/mjpeg_streaming_server.py create mode 100644 psylib/psylib/other.py create mode 100644 psylib/setup.py diff --git a/psylib/psylib/__init__.py b/psylib/psylib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/psylib/psylib/mjpeg_streaming_server.py b/psylib/psylib/mjpeg_streaming_server.py new file mode 100644 index 0000000..c36a9df --- /dev/null +++ b/psylib/psylib/mjpeg_streaming_server.py @@ -0,0 +1,169 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# This file is part of chaosc and psychosis +# +# chaosc/psychosis is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# chaosc/psychosis is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with chaosc/psychosis. If not, see . +# +# Copyright (C) 2014 Stefan Kögl + +from __future__ import absolute_import + +import os +import os.path +import re +import sys + +from datetime import datetime +from chaosc.argparser_groups import * +from chaosc.lib import logger, resolve_host +from PyQt4 import QtCore, QtGui +from PyQt4.QtCore import QBuffer, QByteArray, QIODevice +from PyQt4.QtNetwork import QTcpServer, QTcpSocket + +class MjpegStreamingServer(QTcpServer): + + def __init__(self, server_address, parent=None): + super(MjpegStreamingServer, self).__init__(parent) + self.server_address = server_address + self.newConnection.connect(self.new_connection) + self.widget = parent + self.sockets = list() + self.img_data = None + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.send_image) + self.timer.start(80) + self.stream_clients = list() + self.get_regex = re.compile("^GET /(\w+?)\.(\w+?) HTTP/(\d+\.\d+)$") + self.host_regex = re.compile("^Host: (\w+?):(\d+)$") + self.html_map = dict() + + def handle_request(self): + sock = self.sender() + logger.info("handle_request: %s %d", sock.peerAddress(), sock.peerPort()) + sock_id = id(sock) + if sock.state() in (QTcpSocket.UnconnectedState, QTcpSocket.ClosingState): + logger.info("connection closed") + self.sockets.remove(sock) + sock.deleteLater() + return + + client_data = str(sock.readAll()) + logger.info("request %r", client_data) + line = client_data.split("\r\n")[0] + logger.info("first line: %r", line) + try: + resource, ext, http_version = self.get_regex.match(line).groups() + logger.info("resource=%r, ext=%r, http_version=%r", resource, ext, http_version) + except AttributeError: + try: + host, port = self.host_regex.match(line).groups() + logger.info("found host header %r %r", host, port) + #return + #sock.write("HTTP/1.1 501 Not Implemented\r\n") + return + except AttributeError: + logger.info("no matching request - sending 404 not found") + sock.write("HTTP/1.1 404 Not Found\r\n") + return + else: + if ext == "ico": + directory = os.path.dirname(os.path.abspath(__file__)) + data = open(os.path.join(directory, "favicon.ico"), "rb").read() + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data)) + elif ext == "html": + directory = os.path.dirname(os.path.abspath(__file__)) + data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id + self.html_map[sock_id] = None + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: text/html;encoding: utf-8\r\n\r\n%s' % data)) + elif ext == "mjpeg": + try: + _, html_sock_id = resource.split("_", 1) + html_sock_id = int(html_sock_id) + except ValueError: + html_sock_id = None + + if sock not in self.stream_clients: + logger.info("starting streaming...") + if html_sock_id is not None: + self.html_map[html_sock_id] = sock + self.stream_clients.append(sock) + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: multipart/x-mixed-replace; boundary=--2342\r\n\r\n')) + else: + logger.error("request not found/handled - sending 404 not found") + sock.write("HTTP/1.1 404 Not Found\r\n") + + def slot_remove_connection(self): + try: + sock = self.sender() + except RuntimeError: + return + if sock.state() == QTcpSocket.UnconnectedState: + self.__remove_connection(sock) + + def __remove_connection(self, sock): + sock_id = id(sock) + sock.disconnected.disconnect(self.slot_remove_connection) + sock.close() + sock.deleteLater() + self.sockets.remove(sock) + logger.info("connection removed: sock=%r, sock_id=%r", sock, sock_id) + + try: + self.stream_clients.remove(sock) + except ValueError: + pass + + try: + stream_client = self.html_map.pop(sock_id) + except KeyError: + logger.info("socket has no child socket") + else: + stream_client.close() + try: + self.stream_clients.remove(stream_client) + logger.info("removed stream_client=%r", id(stream_client)) + except ValueError: + pass + + try: + self.sockets.remove(stream_client) + logger.info("removed child sock_id=%r", id(stream_client)) + except ValueError: + pass + + + def send_image(self): + if not self.stream_clients: + return + + img_data = self.widget.render_image() + len_data = len(img_data) + array = QByteArray("--2342\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len_data, img_data)) + for sock in self.stream_clients: + sock.write(array) + + def new_connection(self): + while self.hasPendingConnections(): + sock = self.nextPendingConnection() + logger.info("new connection=%r", id(sock)) + sock.readyRead.connect(self.handle_request) + sock.disconnected.connect(self.slot_remove_connection) + self.sockets.append(sock) + + def stop(self): + self.stream_clients = list() + self.sockets = list() + self.html_map = dict() + self.close() diff --git a/psylib/psylib/other.py b/psylib/psylib/other.py new file mode 100644 index 0000000..3140be1 --- /dev/null +++ b/psylib/psylib/other.py @@ -0,0 +1,164 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# This file is part of chaosc and psychosis +# +# chaosc/psychosis is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# chaosc/psychosis is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with chaosc/psychosis. If not, see . +# +# Copyright (C) 2014 Stefan Kögl + +from __future__ import absolute_import + +import os +import os.path +import re +import sys + +from datetime import datetime +from chaosc.argparser_groups import * +from chaosc.lib import logger, resolve_host +from PyQt4 import QtCore, QtGui +from PyQt4.QtCore import QBuffer, QByteArray, QIODevice +from PyQt4.QtNetwork import QTcpServer + +class MjpegStreamingServer(QTcpServer): + + def __init__(self, server_address, parent=None): + super(MjpegStreamingServer, self).__init__(parent) + self.server_address = server_address + self.newConnection.connect(self.new_connection) + self.widget = parent + self.win_id = self.widget.winId() + self.sockets = list() + self.img_data = None + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.render_image) + self.timer.start(80) + self.stream_clients = list() + self.get_regex = re.compile("^GET /(\w+?)\.(\w+?) HTTP/(\d+\.\d+)$") + self.host_regex = re.compile("^Host: (\w+?):(\d+)$") + self.html_map = dict() + + def handle_request(self): + sock = self.sender() + logger.info("handle_request: %s %d", sock.peerAddress(), sock.peerPort()) + sock_id = id(sock) + if sock.state() in (QTcpSocket.UnconnectedState, QTcpSocket.ClosingState): + logger.info("connection closed") + self.sockets.remove(sock) + sock.deleteLater() + return + + client_data = str(sock.readAll()) + logger.info("request %r", client_data) + line = client_data.split("\r\n")[0] + logger.info("first line: %r", line) + try: + resource, ext, http_version = self.get_regex.match(line).groups() + logger.info("resource=%r, ext=%r, http_version=%r", resource, ext, http_version) + except AttributeError: + try: + host, port = self.host_regex.match(line).groups() + print "found host header", host, port + return + #sock.write("HTTP/1.1 501 Not Implemented\r\n") + except AttributeError: + logger.info("no matching request - sending 404 not found") + sock.write("HTTP/1.1 404 Not Found\r\n") + return + else: + if ext == "ico": + directory = os.path.dirname(os.path.abspath(__file__)) + data = open(os.path.join(directory, "favicon.ico"), "rb").read() + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data)) + elif ext == "html": + directory = os.path.dirname(os.path.abspath(__file__)) + data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id + self.html_map[sock_id] = None + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: text/html;encoding: utf-8\r\n\r\n%s' % data)) + elif ext == "mjpeg": + try: + _, html_sock_id = resource.split("_", 1) + html_sock_id = int(html_sock_id) + except ValueError: + html_sock_id = None + + if sock not in self.stream_clients: + logger.info("starting streaming...") + if html_sock_id is not None: + self.html_map[html_sock_id] = sock + self.stream_clients.append(sock) + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: multipart/x-mixed-replace; boundary=--2342\r\n\r\n')) + else: + logger.error("request not found/handled - sending 404 not found") + sock.write("HTTP/1.1 404 Not Found\r\n") + + def remove_connection(self): + try: + sock = self.sender() + except RuntimeError: + return + sock_id = id(sock) + logger.info("remove_connection: sock=%r, sock_id=%r", sock, sock_id) + if sock.state() == QTcpSocket.UnconnectedState: + sock.disconnected.disconnect(self.remove_connection) + self.sockets.remove(sock) + logger.info("removed sock_id=%r", sock_id) + sock.close() + try: + self.stream_clients.remove(sock) + except ValueError: + pass + + try: + stream_client = self.html_map.pop(sock_id) + except KeyError: + logger.info("socket has no child socket") + else: + stream_client.close() + try: + self.stream_clients.remove(stream_client) + logger.info("removed stream_client=%r", id(stream_client)) + except ValueError: + pass + + try: + self.sockets.remove(stream_client) + logger.info("removed child sock_id=%r", id(stream_client)) + except ValueError: + pass + + def render_image(self): + if not self.stream_clients: + return + + img_data = self.widget.render_image() + len_data = len(img_data) + array = QByteArray("--2342\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len_data, img_data)) + for sock in self.stream_clients: + sock.write(array) + + def new_connection(self): + while self.hasPendingConnections(): + sock = self.nextPendingConnection() + logger.info("new connection=%r", id(sock)) + sock.readyRead.connect(self.handle_request) + sock.disconnected.connect(self.remove_connection) + self.sockets.append(sock) + + def stop(self): + self.stream_clients = list() + self.sockets = list() + self.html_map = dict() + self.close() diff --git a/psylib/setup.py b/psylib/setup.py new file mode 100644 index 0000000..40332a1 --- /dev/null +++ b/psylib/setup.py @@ -0,0 +1,43 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from distribute_setup import use_setuptools +use_setuptools() + +import sys +from setuptools import find_packages, setup + +if sys.version_info >= (3,): + extras['use_2to3'] = True + +setup( + name='psylib', + version="0.1", + packages=find_packages(exclude=["scripts",]), + + include_package_data = True, + + exclude_package_data = {'': ['.gitignore']}, + + zip_safe = False, + + # pypi metadata + author = "Stefan Kögl", + + # FIXME: add author email + author_email = "hotte@ctdo.de", + description = "library for psychosis", + + # FIXME: add long_description + long_description = """ + """, + + # FIXME: add license + license = "GPL", + + # FIXME: add keywords + keywords = "", + + # FIXME: add download url + url = "", +) From 80d6aea666f36e8ba6cbd1e7a4b51cb4b3610904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Mon, 26 May 2014 23:25:36 +0200 Subject: [PATCH 5/7] much rewrite, very kane :) --- dump_grabber/dump_grabber/index.html | 9 + dump_grabber/dump_grabber/main.py | 106 +++++------- ekgplotter/ekgplotter/index.html | 2 +- ekgplotter/ekgplotter/main_qt.py | 206 ++++++++++------------ psylib/psylib/mjpeg_streaming_server.py | 109 ++++++++---- psylib/psylib/psyqt_base.py | 105 ++++++++++++ texter/texter/main.py | 219 ++++++------------------ 7 files changed, 365 insertions(+), 391 deletions(-) create mode 100644 dump_grabber/dump_grabber/index.html create mode 100644 psylib/psylib/psyqt_base.py diff --git a/dump_grabber/dump_grabber/index.html b/dump_grabber/dump_grabber/index.html new file mode 100644 index 0000000..df3af71 --- /dev/null +++ b/dump_grabber/dump_grabber/index.html @@ -0,0 +1,9 @@ + + + + + + + Smiley face + + diff --git a/dump_grabber/dump_grabber/main.py b/dump_grabber/dump_grabber/main.py index 06d0701..535cab3 100644 --- a/dump_grabber/dump_grabber/main.py +++ b/dump_grabber/dump_grabber/main.py @@ -23,8 +23,9 @@ from __future__ import absolute_import import os import os.path import re +import signal import sys - +from collections import deque from datetime import datetime from chaosc.argparser_groups import * from chaosc.lib import logger, resolve_host @@ -33,7 +34,8 @@ from PyQt4.QtCore import QBuffer, QByteArray, QIODevice from PyQt4.QtNetwork import QTcpServer, QTcpSocket, QUdpSocket, QHostAddress from dump_grabber.dump_grabber_ui import Ui_MainWindow -from psylib.mjpeg_streaming_server import MjpegStreamingServer +from psylib.mjpeg_streaming_server import * +from psylib.psyqt_base import PsyQtChaoscClientBase try: from chaosc.c_osc_lib import OSCMessage, decode_osc @@ -42,62 +44,17 @@ except ImportError as e: app = QtGui.QApplication([]) -class TextStorage(object): - def __init__(self, columns): - super(TextStorage, self).__init__() +class ExclusiveTextStorage(object): + def __init__(self, columns, default_font, column_width, line_height, scene): self.column_count = columns self.colors = (QtCore.Qt.red, QtCore.Qt.green, QtGui.QColor(46, 100, 254)) - - def init_columns(self): - raise NotImplementedError() - - def add_text(self, column, text): - raise NotImplementedError() - - -class ColumnTextStorage(TextStorage): - def __init__(self, columns, default_font, column_width, line_height, scene): - super(ColumnTextStorage, self).__init__(columns) - self.columns = list() - self.default_font = default_font - self.column_width = column_width - self.line_height = line_height - self.graphics_scene = scene - self.num_lines, self.offset = divmod(768, self.line_height) - - def init_columns(self): - for x in range(self.column_count): - column = list() - color = self.colors[x] - for y in range(self.num_lines): - text_item = self.graphics_scene.addSimpleText("%d:%d" % (x, y), self.default_font) - text_item.setBrush(color) - text_item.setPos(x * self.column_width, y * self.line_height) - column.append(text_item) - self.columns.append(column) - - def add_text(self, column, text): - text_item = self.graphics_scene.addSimpleText(text, self.default_font) - color = self.colors[column] - text_item.setBrush(color) - - old_item = self.columns[column].pop(0) - self.graphics_scene.removeItem(old_item) - self.columns[column].append(text_item) - for iy, text_item in enumerate(self.columns[column]): - text_item.setPos(column * self.column_width, iy * self.line_height) - - -class ExclusiveTextStorage(TextStorage): - def __init__(self, columns, default_font, column_width, line_height, scene): - super(ExclusiveTextStorage, self).__init__(columns) - self.column_count = columns - self.lines = list() + self.lines = deque() self.default_font = default_font self.column_width = column_width self.line_height = line_height self.graphics_scene = scene self.num_lines, self.offset = divmod(576, self.line_height) + self.data = deque() def init_columns(self): color = self.colors[0] @@ -107,26 +64,38 @@ class ExclusiveTextStorage(TextStorage): text_item.setPos(0, y * self.line_height) self.lines.append(text_item) - def add_text(self, column, text): + def __add_text(self, column, text): text_item = self.graphics_scene.addSimpleText(text, self.default_font) text_item.setX(column * self.column_width) color = self.colors[column] text_item.setBrush(color) - old_item = self.lines.pop(0) + old_item = self.lines.popleft() self.graphics_scene.removeItem(old_item) self.lines.append(text_item) + + def finish(self): + while 1: + try: + column, text = self.data.popleft() + self.__add_text(column, text) + except IndexError, msg: + break + + for iy, text_item in enumerate(self.lines): text_item.setY(iy * self.line_height) + def add_text(self, column, text): + self.data.append((column, text)) - - -class MainWindow(QtGui.QMainWindow, Ui_MainWindow): - def __init__(self, args, parent=None, columns=3, column_exclusive=False): - super(MainWindow, self).__init__(parent) +class MainWindow(QtGui.QMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, PsyQtChaoscClientBase): + def __init__(self, args, parent=None): self.args = args + #PsyQtChaoscClientBase.__init__(self) + super(MainWindow, self).__init__() + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.setupUi(self) self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self) @@ -140,20 +109,16 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.graphics_view.setScene(self.graphics_scene) self.default_font = QtGui.QFont("Monospace", 14) self.default_font.setStyleHint(QtGui.QFont.Monospace) - self.default_font.setBold(True) + #self.default_font.setBold(True) self.graphics_scene.setFont(self.default_font) self.font_metrics = QtGui.QFontMetrics(self.default_font) self.line_height = self.font_metrics.height() + columns = 3 self.column_width = 775 / columns self.text_storage = ExclusiveTextStorage(columns, self.default_font, self.column_width, self.line_height, self.graphics_scene) self.text_storage.init_columns() - self.osc_sock = QUdpSocket(self) - logger.info("osc bind localhost %d", args.client_port) - self.osc_sock.bind(QHostAddress("127.0.0.1"), args.client_port) - self.osc_sock.readyRead.connect(self.got_message) - self.osc_sock.error.connect(self.handle_osc_error) msg = OSCMessage("/subscribe") msg.appendTypedArg("localhost", "s") msg.appendTypedArg(args.client_port, "i") @@ -165,6 +130,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.regex = re.compile("^/(uwe|merle|bjoern)/(.*?)$") + def pubdir(self): + return os.path.dirname(os.path.abspath(__file__)) + def closeEvent(self, event): msg = OSCMessage("/unsubscribe") msg.appendTypedArg("localhost", "s") @@ -179,6 +147,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.text_storage.add_text(column, text) def render_image(self): + self.text_storage.finish() image = QtGui.QImage(768, 576, QtGui.QImage.Format_ARGB32_Premultiplied) image.fill(QtCore.Qt.black) painter = QtGui.QPainter(image) @@ -191,7 +160,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): painter.end() buf = QBuffer() buf.open(QIODevice.WriteOnly) - image.save(buf, "JPG", 80) + image.save(buf, "JPG", 85) image_data = buf.data() return image_data @@ -207,6 +176,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): except AttributeError: pass else: + if text == "temperatur": + text += "e" if actor == "merle": self.add_text(0, "%s = %s" % (text, ", ".join([str(i) for i in args]))) elif actor == "uwe": @@ -229,8 +200,11 @@ def main(): args = arg_parser.finalize() http_host, http_port = resolve_host(args.http_host, args.http_port, args.address_family) + args.chaosc_host, args.chaosc_port = resolve_host(args.chaosc_host, args.chaosc_port, args.address_family) + window = MainWindow(args) - #window.show() + sys.excepthook = window.sigint_handler + signal.signal(signal.SIGTERM, window.sigterm_handler) app.exec_() diff --git a/ekgplotter/ekgplotter/index.html b/ekgplotter/ekgplotter/index.html index 3f46bbb..81a9f75 100644 --- a/ekgplotter/ekgplotter/index.html +++ b/ekgplotter/ekgplotter/index.html @@ -1,7 +1,7 @@ -Smiley face +Smiley face diff --git a/ekgplotter/ekgplotter/main_qt.py b/ekgplotter/ekgplotter/main_qt.py index bec7536..ce03886 100644 --- a/ekgplotter/ekgplotter/main_qt.py +++ b/ekgplotter/ekgplotter/main_qt.py @@ -24,79 +24,82 @@ from __future__ import absolute_import -from chaosc.argparser_groups import * -from chaosc.lib import logger, resolve_host -from datetime import datetime -from operator import attrgetter -from PyQt4 import QtGui, QtCore -from PyQt4.QtCore import QBuffer, QByteArray, QIODevice -from PyQt4.QtNetwork import QTcpServer, QTcpSocket, QUdpSocket, QHostAddress -from PyQt4.QtGui import QPixmap - -import logging -import numpy as np +import atexit +import random import os.path +import re +import signal +import sys +import exceptions + +from PyQt4 import QtCore, QtGui +from PyQt4.QtCore import QBuffer, QByteArray, QIODevice +from PyQt4.QtNetwork import QUdpSocket, QHostAddress + +import numpy as np + import pyqtgraph as pg from pyqtgraph.widgets.PlotWidget import PlotWidget -import Queue -import re -import select -import socket -import threading -import time - -from psylib.mjpeg_streaming_server import MjpegStreamingServer +from chaosc.argparser_groups import ArgParser +from chaosc.lib import logger, resolve_host +from psylib.mjpeg_streaming_server import * +from psylib.psyqt_base import PsyQtChaoscClientBase try: from chaosc.c_osc_lib import OSCMessage, decode_osc except ImportError as e: - logging.exception(e) from chaosc.osc_lib import OSCMessage, decode_osc +qtapp = QtGui.QApplication([]) -def get_steps(pulse_rate, rate): - beat_length = 60. / pulse_rate - steps_pre = int(beat_length / rate) + 1 + +def get_steps(pulse, delta_ms): + beat_length = 60000. / pulse + steps_pre = int(beat_length / delta_ms) + 1 used_sleep_time = beat_length / steps_pre steps = int(beat_length / used_sleep_time) return steps, used_sleep_time class Generator(object): - def __init__(self, pulse=92, delta=0.08): + def __init__(self, pulse=92, delta=80): self.count = 0 - self.pulse = 92 + self.pulse = random.randint(85, 105) self.delta = delta - self.steps = get_steps(self.pulse, delta / 2) + self.multiplier = 4 + self.steps, _ = get_steps(self.pulse, delta / self.multiplier) def __call__(self): while 1: - value = random.randint(0, steps) - if self.count < int(steps / 100. * 20): - value = random.randint(0,20) - elif self.count < int(steps / 100. * 30): - value = random.randint(20, 30) - elif self.count < int(steps / 100. * 40): - value = random.randint(30,100) - elif self.count < int(steps / 2.): - value = random.randint(100,200) - elif self.count == int(steps / 2.): + if self.count < int(self.steps / 100. * 30): + value = random.randint(30, 35) + elif self.count == int(self.steps / 100. * 30): + value = random.randint(random.randint(50,60), random.randint(60, 70)) + elif self.count < int(self.steps / 100. * 45): + value = random.randint(30, 35) + elif self.count < int(self.steps / 2.): + value = random.randint(0, 15) + elif self.count == int(self.steps / 2.): value = 255 - elif self.count < int(steps / 100. * 60): - value = random.randint(100, 200) - elif self.count < int(steps / 100. * 70): - value = random.randint(50, 100) - elif self.count < int(steps / 100. * 80): - value = random.randint(20, 50) - elif self.count <= steps: - value = random.randint(0,20) - elif self.count >= steps: + elif self.count < int(self.steps / 100. * 60): + value = random.randint(random.randint(25,30), random.randint(30, 35)) + elif self.count < int(self.steps / 100. * 70): + value = random.randint(random.randint(10,25), random.randint(25, 30)) + elif self.count < self.steps: + value = random.randint(random.randint(15,25), random.randint(25, 30)) + else: self.count = 0 + value = 30 self.count += 1 yield value + def set_pulse(self, pulse): + self.pulse = pulse + self.steps, _ = get_steps(pulse, self.delta / self.multiplier) + + def retrigger(self): self.count = self.steps / 2 @@ -115,11 +118,14 @@ class Actor(object): self.data = np.array([self.offset] * num_data) self.head = 0 self.pre_head = 0 - self.plotItem = pg.PlotCurveItem(pen=pg.mkPen(color, width=3), name=name) + self.plotItem = pg.PlotCurveItem(pen=pg.mkPen(color, width=3), width=4, name=name) + #self.plotItem.setShadowPen(pg.mkPen("w", width=5)) self.plotPoint = pg.ScatterPlotItem(pen=pg.mkPen("w", width=5), brush=pg.mkBrush(color), size=5) + self.osci = None + self.osci_obj = None def __str__(self): - return "" % (self.name, self.active, self.head) + return "" % (self.name, self.head) __repr__ = __str__ @@ -143,29 +149,20 @@ class Actor(object): def render(self): self.plotItem.setData(y=self.data, clear=True) - self.plotPoint.setData(x=[self.pre_head], y = [self.data[self.pre_head]]) + self.plotPoint.setData(x=[self.pre_head], y=[self.data[self.pre_head]]) -class EkgPlotWidget(PlotWidget): +class EkgPlotWidget(PlotWidget, MjpegStreamingConsumerInterface, PsyQtChaoscClientBase): def __init__(self, args, parent=None): - super(EkgPlotWidget, self).__init__(parent) self.args = args + PsyQtChaoscClientBase.__init__(self) + super(EkgPlotWidget, self).__init__() + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self) + self.fps = 12.5 + self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self, self.fps) self.http_server.listen(port=args.http_port) - self.osc_sock = QUdpSocket(self) - logger.info("osc bind localhost %d", args.client_port) - self.osc_sock.bind(QHostAddress("127.0.0.1"), args.client_port) - self.osc_sock.readyRead.connect(self.got_message) - self.osc_sock.error.connect(self.handle_osc_error) - msg = OSCMessage("/subscribe") - msg.appendTypedArg("localhost", "s") - msg.appendTypedArg(args.client_port, "i") - msg.appendTypedArg(self.args.authenticate, "s") - if self.args.subscriber_label is not None: - msg.appendTypedArg(self.args.subscriber_label, "s") - self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110) self.num_data = 100 self.hide() @@ -173,8 +170,6 @@ class EkgPlotWidget(PlotWidget): self.setYRange(0, 255) self.setXRange(0, self.num_data) self.resize(768, 576) - - colors = ["r", "g", "b"] ba = self.getAxis("bottom") @@ -186,10 +181,9 @@ class EkgPlotWidget(PlotWidget): self.active_actors = list() self.actors = dict() - self.lengths1 = [0] self.max_value = 255 - actor_names = ["merle", "uwe", "bjoern" ] + actor_names = ["merle", "uwe", "bjoern"] self.max_actors = len(actor_names) self.actor_height = self.max_value / self.max_actors @@ -197,11 +191,10 @@ class EkgPlotWidget(PlotWidget): self.add_actor(actor_name, self.num_data, color, ix, self.max_actors, self.actor_height) self.set_positions() + self.heartbeat_regex = re.compile("^/(.*?)/heartbeat$") - self.ekg_regex = re.compile("^/(.*?)/ekg$") - self.ctl_regex = re.compile("^/plot/(.*?)$") - self.updated_actors = set() - self.new_round() + def pubdir(self): + return os.path.dirname(os.path.abspath(__file__)) def add_actor(self, actor_name, num_data, color, ix, max_actors, actor_height): @@ -210,7 +203,8 @@ class EkgPlotWidget(PlotWidget): self.addItem(actor_obj.plotItem) self.addItem(actor_obj.plotPoint) self.active_actors.append(actor_obj) - + actor_obj.osci_obj = Generator(delta=self.http_server.timer_delta) + actor_obj.osci = actor_obj.osci_obj() def set_positions(self): for ix, actor_obj in enumerate(self.active_actors): @@ -220,51 +214,24 @@ class EkgPlotWidget(PlotWidget): def active_actor_count(self): return self.max_actors - def new_round(self): - for ix, actor in enumerate(self.active_actors): - actor.updated = 0 + def update(self, osc_address, args): - def update_missing_actors(self): - liste = sorted(self.active_actors, key=attrgetter("updated")) - max_values = liste[-1].updated - if max_values == 0: - # handling no signal - for actor in self.active_actors: - actor.add_value(0) - return - for ix, actor in enumerate(self.active_actors): - diff = max_values - actor.updated - if diff > 0: - for i in range(diff): - actor.add_value(0) - - - def update(self, osc_address, value): - - res = self.ekg_regex.match(osc_address) + res = self.heartbeat_regex.match(osc_address) if res: actor_name = res.group(1) actor_obj = self.actors[actor_name] - actor_obj.add_value(value) + #logger.info("actor: %r, %r", actor_name, args) + if args[0] == 1: + actor_obj.osci_obj.retrigger() + actor_obj.osci_obj.set_pulse(args[1]) - def render(self): - for ix, actor in enumerate(self.active_actors): - actor.render() - - def closeEvent(self, event): - msg = OSCMessage("/unsubscribe") - msg.appendTypedArg("localhost", "s") - msg.appendTypedArg(self.args.client_port, "i") - msg.appendTypedArg(self.args.authenticate, "s") - self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110) - - def handle_osc_error(self, error): - logger.info("osc socket error %d", error) - def render_image(self): - self.update_missing_actors() - self.render() + for actor_obj in self.active_actors: + osc = actor_obj.osci + for i in range(actor_obj.osci_obj.multiplier): + actor_obj.add_value(osc.next()) + actor_obj.render() exporter = pg.exporters.ImageExporter.ImageExporter(self.plotItem) exporter.parameters()['width'] = 768 img = exporter.export(toBytes=True) @@ -272,7 +239,6 @@ class EkgPlotWidget(PlotWidget): buf.open(QIODevice.WriteOnly) img.save(buf, "JPG", 75) JpegData = buf.data() - self.new_round() return JpegData def got_message(self): @@ -282,10 +248,8 @@ class EkgPlotWidget(PlotWidget): osc_address, typetags, args = decode_osc(data, 0, len(data)) except Exception, e: logger.exception(e) - return else: - self.update(osc_address, args[0]) - return True + self.update(osc_address, args) @@ -293,18 +257,20 @@ def main(): arg_parser = ArgParser("ekgplotter") arg_parser.add_global_group() client_group = arg_parser.add_client_group() - arg_parser.add_argument(client_group, '-x', "--http_host", default="::", - help='my host, defaults to "::"') - arg_parser.add_argument(client_group, '-X', "--http_port", default=9000, - type=int, help='my port, defaults to 9000') + arg_parser.add_argument(client_group, '-x', "--http_host", default='::', + help='my host, defaults to "::"') + arg_parser.add_argument(client_group, '-X', '--http_port', default=9000, + type=int, help='my port, defaults to 9000') arg_parser.add_chaosc_group() arg_parser.add_subscriber_group() args = arg_parser.finalize() args.http_host, args.http_port = resolve_host(args.http_host, args.http_port, args.address_family) + args.chaosc_host, args.chaosc_port = resolve_host(args.chaosc_host, args.chaosc_port, args.address_family) - qtapp = QtGui.QApplication([]) - widget = EkgPlotWidget(args) + window = EkgPlotWidget(args) + sys.excepthook = window.sigint_handler + signal.signal(signal.SIGTERM, window.sigterm_handler) qtapp.exec_() diff --git a/psylib/psylib/mjpeg_streaming_server.py b/psylib/psylib/mjpeg_streaming_server.py index c36a9df..9cadd99 100644 --- a/psylib/psylib/mjpeg_streaming_server.py +++ b/psylib/psylib/mjpeg_streaming_server.py @@ -1,20 +1,20 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# This file is part of chaosc and psychosis +# This file is part of chaosc/psylib package # -# chaosc/psychosis is free software: you can redistribute it and/or modify +# chaosc/psylib is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# chaosc/psychosis is distributed in the hope that it will be useful, +# chaosc/psylib is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with chaosc/psychosis. If not, see . +# along with chaosc/psylib. If not, see . # # Copyright (C) 2014 Stefan Kögl @@ -23,27 +23,47 @@ from __future__ import absolute_import import os import os.path import re -import sys -from datetime import datetime -from chaosc.argparser_groups import * -from chaosc.lib import logger, resolve_host -from PyQt4 import QtCore, QtGui -from PyQt4.QtCore import QBuffer, QByteArray, QIODevice +from chaosc.lib import logger +from PyQt4 import QtCore +from PyQt4.QtCore import QByteArray from PyQt4.QtNetwork import QTcpServer, QTcpSocket +__all__ = ["MjpegStreamingConsumerInterface", "MjpegStreamingServer"] + +class MjpegStreamingConsumerInterface(object): + def pubdir(self): + """ returns the directory, from where your static files should be served + + fast and dirty implementation e.g: + return os.path.dirname(os.path.abspath(__file__)) + """ + + raise NotImplementedError() + + def render_image(self): + """returns a QByteArray with the binary date of a jpg image + + this method should implement the actual window/widget grabbing""" + + raise NotImplementedError() + class MjpegStreamingServer(QTcpServer): - def __init__(self, server_address, parent=None): + def __init__(self, server_address, parent=None, fps=12.5): super(MjpegStreamingServer, self).__init__(parent) self.server_address = server_address self.newConnection.connect(self.new_connection) + assert isinstance(parent, MjpegStreamingConsumerInterface) self.widget = parent + self.sockets = list() self.img_data = None + self.fps = fps + self.timer_delta = 1000 / fps self.timer = QtCore.QTimer() self.timer.timeout.connect(self.send_image) - self.timer.start(80) + self.timer.start(self.timer_delta) self.stream_clients = list() self.get_regex = re.compile("^GET /(\w+?)\.(\w+?) HTTP/(\d+\.\d+)$") self.host_regex = re.compile("^Host: (\w+?):(\d+)$") @@ -51,8 +71,8 @@ class MjpegStreamingServer(QTcpServer): def handle_request(self): sock = self.sender() - logger.info("handle_request: %s %d", sock.peerAddress(), sock.peerPort()) sock_id = id(sock) + logger.info("handle_request: sock_id=%r", sock_id) if sock.state() in (QTcpSocket.UnconnectedState, QTcpSocket.ClosingState): logger.info("connection closed") self.sockets.remove(sock) @@ -79,14 +99,26 @@ class MjpegStreamingServer(QTcpServer): return else: if ext == "ico": - directory = os.path.dirname(os.path.abspath(__file__)) - data = open(os.path.join(directory, "favicon.ico"), "rb").read() - sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data)) + directory = self.widget.pubdir() + try: + data = open(os.path.join(directory, "favicon.ico"), "rb").read() + except IOError: + logger.error("request not found/handled - sending 404 not found") + sock.write("HTTP/1.1 404 Not Found\r\n") + return + else: + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data)) elif ext == "html": - directory = os.path.dirname(os.path.abspath(__file__)) - data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id - self.html_map[sock_id] = None - sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: text/html;encoding: utf-8\r\n\r\n%s' % data)) + directory = self.widget.pubdir() + try: + data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id + self.html_map[sock_id] = None + except IOError: + logger.error("request not found/handled - sending 404 not found") + sock.write("HTTP/1.1 404 Not Found\r\n") + return + else: + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: text/html;encoding: utf-8\r\n\r\n%s' % data)) elif ext == "mjpeg": try: _, html_sock_id = resource.split("_", 1) @@ -117,31 +149,40 @@ class MjpegStreamingServer(QTcpServer): sock.disconnected.disconnect(self.slot_remove_connection) sock.close() sock.deleteLater() - self.sockets.remove(sock) - logger.info("connection removed: sock=%r, sock_id=%r", sock, sock_id) + try: + self.sockets.remove(sock) + logger.info("connection %r removed", sock_id) + except ValueError, msg: + logger.info("connection %r was not stored?", sock_id) + try: self.stream_clients.remove(sock) except ValueError: - pass + logger.info("connection %r was not streaming", sock_id) + try: stream_client = self.html_map.pop(sock_id) except KeyError: - logger.info("socket has no child socket") + logger.info("socket %r has no child socket", sock_id) else: - stream_client.close() try: - self.stream_clients.remove(stream_client) - logger.info("removed stream_client=%r", id(stream_client)) - except ValueError: - pass + stream_client.close() + except AttributeError, msg: + logger.info("no stream client") + else: + try: + self.stream_clients.remove(stream_client) + logger.info("child connection %r removed from streaming", id(stream_client)) + except ValueError: + pass - try: - self.sockets.remove(stream_client) - logger.info("removed child sock_id=%r", id(stream_client)) - except ValueError: - pass + try: + self.sockets.remove(stream_client) + logger.info("child connection %r removed from storage", id(stream_client)) + except ValueError: + pass def send_image(self): diff --git a/psylib/psylib/psyqt_base.py b/psylib/psylib/psyqt_base.py new file mode 100644 index 0000000..4d5506e --- /dev/null +++ b/psylib/psylib/psyqt_base.py @@ -0,0 +1,105 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# This file is part of chaosc/psylib package +# +# chaosc/psylib is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# chaosc/psylib is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with chaosc/psylib. If not, see . +# +# Copyright (C) 2014 Stefan Kögl + +from __future__ import absolute_import + +import sys + +from PyQt4 import QtCore, QtGui +from PyQt4.QtCore import QBuffer, QByteArray +from PyQt4.QtNetwork import QUdpSocket, QHostAddress + + +from chaosc.lib import logger + +try: + from chaosc.c_osc_lib import OSCMessage, decode_osc +except ImportError as e: + from chaosc.osc_lib import OSCMessage, decode_osc + + +class PsyQtClientBase(QtCore.QObject): + + def __init__(self): + super(PsyQtClientBase, self).__init__() + # periodically trap into python interpreter domain to catch signals etc + timer = QtCore.QTimer() + timer.start(2000) + timer.timeout.connect(lambda: None) + + def sigint_handler(self, ex_cls, ex, traceback): + """Handler for the SIGINT signal.""" + if ex_cls == KeyboardInterrupt: + logger.info("found KeyboardInterrupt") + QtGui.QApplication.exit() + else: + logger.critical(''.join(traceback.format_tb(tb))) + logger.critical('{0}: {1}'.format(ex_cls, ex)) + +class PsyQtChaoscClientBase(PsyQtClientBase): + + def __init__(self): + super(PsyQtChaoscClientBase, self).__init__() + self.osc_sock = QUdpSocket(self) + logger.info("osc bind localhost %d", self.args.client_port) + self.osc_sock.bind(QHostAddress(self.args.client_host), self.args.client_port) + self.osc_sock.readyRead.connect(self.got_message) + self.osc_sock.error.connect(self.handle_osc_error) + self.subscribe() + + def sigint_handler(self, ex_cls, ex, traceback): + """Handler for the SIGINT signal.""" + if ex_cls == KeyboardInterrupt: + logger.info("found KeyboardInterrupt") + self.unsubscribe() + QtGui.QApplication.exit() + else: + logger.critical(''.join(traceback.format_tb(traceback))) + logger.critical('{0}: {1}'.format(ex_cls, ex)) + + def sigterm_handler(self, *args): + self.unsubscribe() + QtGui.QApplication.exit() + + def subscribe(self): + logger.info("subscribe") + msg = OSCMessage("/subscribe") + msg.appendTypedArg("localhost", "s") + msg.appendTypedArg(self.args.client_port, "i") + msg.appendTypedArg(self.args.authenticate, "s") + if self.args.subscriber_label is not None: + msg.appendTypedArg(self.args.subscriber_label, "s") + self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress(self.args.chaosc_host), self.args.chaosc_port) + + def unsubscribe(self): + logger.info("unsubscribe") + msg = OSCMessage("/unsubscribe") + msg.appendTypedArg("localhost", "s") + msg.appendTypedArg(self.args.client_port, "i") + msg.appendTypedArg(self.args.authenticate, "s") + self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress(self.args.chaosc_host), self.args.chaosc_port) + + def handle_osc_error(self, error): + logger.info("osc socket error %d", error) + + def closeEvent(self, event): + logger.info("closeEvent", event) + self.unsubscribe() + event.accept() diff --git a/texter/texter/main.py b/texter/texter/main.py index de4d46a..923642c 100644 --- a/texter/texter/main.py +++ b/texter/texter/main.py @@ -24,6 +24,7 @@ from __future__ import absolute_import import cPickle import os.path import re +import sys from PyQt4 import QtCore, QtGui @@ -39,11 +40,14 @@ from PyQt4.QtNetwork import QTcpServer, QTcpSocket from chaosc.argparser_groups import ArgParser from chaosc.lib import resolve_host, logger +from psylib.mjpeg_streaming_server import * +from psylib.psyqt_base import PsyQtClientBase + from texter.texter_ui import Ui_MainWindow, _fromUtf8 from texter.edit_dialog_ui import Ui_EditDialog from texter.text_model import TextModel -app = QtGui.QApplication([]) +qtapp = QtGui.QApplication([]) # NOTE: if the QIcon.fromTheme method does not find any icons, you can use @@ -54,144 +58,6 @@ app = QtGui.QApplication([]) def get_preview_text(text): return re.sub(" +", " ", text.replace("\n", " ")).strip()[:20] -class MjpegStreamingServer(QTcpServer): - - def __init__(self, server_address, parent=None): - super(MjpegStreamingServer, self).__init__(parent) - self.server_address = server_address - self.newConnection.connect(self.start_streaming) - self.widget = parent.live_text - self.win_id = self.widget.winId() - self.sockets = list() - self.img_data = None - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.render_image) - self.timer.start(80) - self.stream_clients = list() - self.regex = re.compile("^GET /(\w+?)\.(\w+?) HTTP/(\d+\.\d+)$") - self.html_map = dict() - self.coords = parent.live_text_rect() - - def handle_request(self): - print "foo" - sock = self.sender() - logger.info("handle_request: %s %d", sock.peerAddress(), sock.peerPort()) - sock_id = id(sock) - if sock.state() in (QTcpSocket.UnconnectedState, QTcpSocket.ClosingState): - logger.info("connection closed") - self.sockets.remove(sock) - sock.deleteLater() - return - - client_data = str(sock.readAll()) - logger.info("request %r", client_data) - line = client_data.split("\r\n")[0] - logger.info("first line: %r", line) - try: - resource, ext, http_version = self.regex.match(line).groups() - logger.info("resource=%r, ext=%r, http_version=%r", resource, ext, http_version) - except AttributeError: - logger.info("no matching request - sending 404 not found") - sock.write("HTTP/1.1 404 Not Found\r\n") - else: - if ext == "ico": - directory = os.path.dirname(os.path.abspath(__file__)) - data = open(os.path.join(directory, "favicon.ico"), "rb").read() - sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data)) - elif ext == "html": - directory = os.path.dirname(os.path.abspath(__file__)) - data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id - self.html_map[sock_id] = None - sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: text/html;encoding: utf-8\r\n\r\n%s' % data)) - #sock.close() - elif ext == "mjpeg": - try: - _, html_sock_id = resource.split("_", 1) - html_sock_id = int(html_sock_id) - except ValueError: - html_sock_id = None - - if sock not in self.stream_clients: - logger.info("starting streaming...") - if html_sock_id is not None: - self.html_map[html_sock_id] = sock - self.stream_clients.append(sock) - sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: multipart/x-mixed-replace; boundary=--2342\r\n\r\n')) - else: - logger.error("request not found/handled - sending 404 not found") - sock.write("HTTP/1.1 404 Not Found\r\n") - - - def remove_stream_client(self): - try: - sock = self.sender() - except RuntimeError: - return - sock_id = id(sock) - logger.info("remove_stream_client: sock=%r, sock_id=%r", sock, sock_id) - if sock.state() == QTcpSocket.UnconnectedState: - sock.disconnected.disconnect(self.remove_stream_client) - self.sockets.remove(sock) - logger.info("removed sock_id=%r", sock_id) - sock.close() - try: - self.stream_clients.remove(sock) - except ValueError: - pass - - try: - stream_client = self.html_map.pop(sock_id) - except KeyError: - logger.info("socket has no child socket") - else: - stream_client.close() - try: - self.stream_clients.remove(stream_client) - logger.info("removed stream_client=%r", id(stream_client)) - except ValueError: - pass - - try: - self.sockets.remove(stream_client) - logger.info("removed child sock_id=%r", id(stream_client)) - except ValueError: - pass - - def render_image(self): - if not self.stream_clients: - return - - #pixmap = QPixmap.grabWidget(self.widget, QtCore.QRect(10, 10, 768, 576)) - pixmap = QPixmap.grabWindow(self.win_id, 5, 5, 768, 576) - buf = QBuffer() - buf.open(QIODevice.WriteOnly) - pixmap.save(buf, "JPG", 30) - self.img_data = buf.data() - len_data = len(self.img_data) - array = QByteArray("--2342\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len_data, self.img_data)) - for sock in self.stream_clients: - sock.write(array) - - def start_streaming(self): - while self.hasPendingConnections(): - sock = self.nextPendingConnection() - logger.info("new connection=%r", id(sock)) - sock.readyRead.connect(self.handle_request) - sock.disconnected.connect(self.remove_stream_client) - self.sockets.append(sock) - - def stop(self): - for sock in self.sockets: - sock.close() - sock.deleteLater() - for sock in self.stream_clients: - sock.close() - sock.deleteLater() - self.stream_clients = list() - self.sockets = list() - self.html_map = dict() - self.close() - class EditDialog(QtGui.QWidget, Ui_EditDialog): def __init__(self, parent=None): @@ -366,10 +232,10 @@ class TextAnimation(QtCore.QObject): self.count += 1 -class MainWindow(KMainWindow, Ui_MainWindow): +class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, PsyQtClientBase): def __init__(self, args, parent=None): - super(MainWindow, self).__init__(parent) self.args = args + super(MainWindow, self).__init__() self.is_streaming = False self.live_center_action = None @@ -391,11 +257,15 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.dialog = None self.current_object = None self.current_index = -1 + self.win_id = self.winId() self.is_auto_publish = False self.setupUi(self) - self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self) + self.coords = self.live_text_rect() + + self.fps = 12.5 + self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self, self.fps) self.live_text.setLineWrapMode(QtGui.QTextEdit.LineWrapMode(QtGui.QTextEdit.FixedPixelWidth)) self.live_text.setLineWrapColumnOrWidth(768) @@ -433,17 +303,29 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.create_toolbar() self.slot_load() - app.focusChanged.connect(self.focusChanged) + qtapp.focusChanged.connect(self.focusChanged) self.start_streaming() self.show() + def pubdir(self): + return os.path.dirname(os.path.abspath(__file__)) + + def getPreviewCoords(self): public_rect = self.preview_text.geometry() global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight())) return global_rect.x(), global_rect.y() + def render_image(self): + pixmap = QPixmap.grabWindow(self.win_id, self.coords.x() + 10, self.coords.y() + 10, 768, 576) + buf = QBuffer() + buf.open(QIODevice.WriteOnly) + pixmap.save(buf, "JPG", 75) + return buf.data() + + def filter_editor_actions(self): disabled_action_names = [ "action_to_plain_text", @@ -631,9 +513,10 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.dialog.setButtons(KDialog.ButtonCodes(KDialog.Ok | KDialog.Cancel)) self.dialog.okClicked.connect(self.slot_save) self.dialog.exec_() + super(self, MainWindow).closeEvent(event) def live_text_rect(self): - return 5, 5, 768, 576 + return self.live_text.geometry() def stop_streaming(self): self.is_streaming = False @@ -643,6 +526,24 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.http_server.listen(port=self.args.http_port) self.is_streaming = True + def fill_combo_box(self): + if self.dialog is not None: + self.dialog.deleteLater() + self.dialog = None + + self.text_combo.clear() + current_row = -1 + for index, list_obj in enumerate(self.model.text_db): + preview, text = list_obj + self.text_combo.addAction(preview) + if list_obj == self.current_object: + current_row = index + + if current_row == -1: + current_row = self.current_index + self.slot_load_preview_text(current_row) + self.text_combo.setCurrentItem(current_row) + def focusChanged(self, old, new): if new == self.preview_text: self.live_editor_collection.clearAssociatedWidgets() @@ -651,13 +552,6 @@ class MainWindow(KMainWindow, Ui_MainWindow): self.preview_editor_collection.clearAssociatedWidgets() self.live_editor_collection.addAssociatedWidget(self.toolbar) - def custom_clear(self, cursor): - cursor.beginEditBlock() - cursor.movePosition(QtGui.QTextCursor.Start) - cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.KeepAnchor) - cursor.removeSelectedText() - cursor.endEditBlock() - def slot_auto_publish(self, state): self.is_auto_publish = bool(state) @@ -737,23 +631,6 @@ class MainWindow(KMainWindow, Ui_MainWindow): if self.fade_animation.timer is None: self.fade_animation.start_animation() - def fill_combo_box(self): - if self.dialog is not None: - self.dialog.deleteLater() - self.dialog = None - - self.text_combo.clear() - current_row = -1 - for index, list_obj in enumerate(self.model.text_db): - preview, text = list_obj - self.text_combo.addAction(preview) - if list_obj == self.current_object: - current_row = index - - if current_row == -1: - current_row = self.current_index - self.slot_load_preview_text(current_row) - self.text_combo.setCurrentItem(current_row) def slot_load_preview_text(self, index): try: @@ -864,9 +741,11 @@ def main(): args = arg_parser.finalize() args.http_host, args.http_port = resolve_host(args.http_host, args.http_port, args.address_family) + args.chaosc_host, args.chaosc_port = resolve_host(args.chaosc_host, args.chaosc_port, args.address_family) - window = MainWindow(args) - app.exec_() + window = MainWindow(args, None) + sys.excepthook = window.sigint_handler + qtapp.exec_() if __name__ == '__main__': From f2963a938d7663d83842b052346b6488e1289352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Thu, 29 May 2014 10:22:04 +0200 Subject: [PATCH 6/7] polished --- dump_grabber/dump_grabber/main.py | 9 ------- ekgplotter/ekgplotter/main_qt.py | 23 ++++++++++------- psylib/psylib/mjpeg_streaming_server.py | 3 +++ psylib/psylib/psyqt_base.py | 7 +++-- texter/texter/edit_dialog.ui | 2 +- texter/texter/edit_dialog_ui.py | 4 +-- texter/texter/main.py | 34 ++++++++++++++++++------- 7 files changed, 50 insertions(+), 32 deletions(-) diff --git a/dump_grabber/dump_grabber/main.py b/dump_grabber/dump_grabber/main.py index 535cab3..396da28 100644 --- a/dump_grabber/dump_grabber/main.py +++ b/dump_grabber/dump_grabber/main.py @@ -119,15 +119,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterfa self.text_storage = ExclusiveTextStorage(columns, self.default_font, self.column_width, self.line_height, self.graphics_scene) self.text_storage.init_columns() - msg = OSCMessage("/subscribe") - msg.appendTypedArg("localhost", "s") - msg.appendTypedArg(args.client_port, "i") - msg.appendTypedArg(self.args.authenticate, "s") - if self.args.subscriber_label is not None: - msg.appendTypedArg(self.args.subscriber_label, "s") - self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110) - #self.add_text(0, "foo bar") - self.regex = re.compile("^/(uwe|merle|bjoern)/(.*?)$") def pubdir(self): diff --git a/ekgplotter/ekgplotter/main_qt.py b/ekgplotter/ekgplotter/main_qt.py index ce03886..5f75b32 100644 --- a/ekgplotter/ekgplotter/main_qt.py +++ b/ekgplotter/ekgplotter/main_qt.py @@ -75,7 +75,7 @@ class Generator(object): if self.count < int(self.steps / 100. * 30): value = random.randint(30, 35) elif self.count == int(self.steps / 100. * 30): - value = random.randint(random.randint(50,60), random.randint(60, 70)) + value = random.randint(55, 66) elif self.count < int(self.steps / 100. * 45): value = random.randint(30, 35) elif self.count < int(self.steps / 2.): @@ -83,11 +83,11 @@ class Generator(object): elif self.count == int(self.steps / 2.): value = 255 elif self.count < int(self.steps / 100. * 60): - value = random.randint(random.randint(25,30), random.randint(30, 35)) + value = random.randint(25, 35) elif self.count < int(self.steps / 100. * 70): - value = random.randint(random.randint(10,25), random.randint(25, 30)) + value = random.randint(10, 30) elif self.count < self.steps: - value = random.randint(random.randint(15,25), random.randint(25, 30)) + value = random.randint(15, 30) else: self.count = 0 value = 30 @@ -152,11 +152,11 @@ class Actor(object): self.plotPoint.setData(x=[self.pre_head], y=[self.data[self.pre_head]]) -class EkgPlotWidget(PlotWidget, MjpegStreamingConsumerInterface, PsyQtChaoscClientBase): +class EkgPlotWidget(PlotWidget, PsyQtChaoscClientBase, MjpegStreamingConsumerInterface): def __init__(self, args, parent=None): self.args = args - PsyQtChaoscClientBase.__init__(self) super(EkgPlotWidget, self).__init__() + PsyQtChaoscClientBase.__init__(self) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.fps = 12.5 @@ -193,6 +193,10 @@ class EkgPlotWidget(PlotWidget, MjpegStreamingConsumerInterface, PsyQtChaoscClie self.set_positions() self.heartbeat_regex = re.compile("^/(.*?)/heartbeat$") + self.pull_timer = QtCore.QTimer() + self.pull_timer.timeout.connect(self.slot_pull_ekg) + self.pull_timer.start(40) + def pubdir(self): return os.path.dirname(os.path.abspath(__file__)) @@ -228,9 +232,6 @@ class EkgPlotWidget(PlotWidget, MjpegStreamingConsumerInterface, PsyQtChaoscClie def render_image(self): for actor_obj in self.active_actors: - osc = actor_obj.osci - for i in range(actor_obj.osci_obj.multiplier): - actor_obj.add_value(osc.next()) actor_obj.render() exporter = pg.exporters.ImageExporter.ImageExporter(self.plotItem) exporter.parameters()['width'] = 768 @@ -251,6 +252,10 @@ class EkgPlotWidget(PlotWidget, MjpegStreamingConsumerInterface, PsyQtChaoscClie else: self.update(osc_address, args) + def slot_pull_ekg(self): + for actor_obj in self.active_actors: + actor_obj.add_value(actor_obj.osci.next()) + def main(): diff --git a/psylib/psylib/mjpeg_streaming_server.py b/psylib/psylib/mjpeg_streaming_server.py index 9cadd99..2146b3d 100644 --- a/psylib/psylib/mjpeg_streaming_server.py +++ b/psylib/psylib/mjpeg_streaming_server.py @@ -205,6 +205,9 @@ class MjpegStreamingServer(QTcpServer): def stop(self): self.stream_clients = list() + for sock in self.sockets: + sock.close() + sock.deleteLater() self.sockets = list() self.html_map = dict() self.close() diff --git a/psylib/psylib/psyqt_base.py b/psylib/psylib/psyqt_base.py index 4d5506e..e429024 100644 --- a/psylib/psylib/psyqt_base.py +++ b/psylib/psylib/psyqt_base.py @@ -21,6 +21,7 @@ from __future__ import absolute_import import sys +import traceback from PyQt4 import QtCore, QtGui from PyQt4.QtCore import QBuffer, QByteArray @@ -64,17 +65,19 @@ class PsyQtChaoscClientBase(PsyQtClientBase): self.osc_sock.error.connect(self.handle_osc_error) self.subscribe() - def sigint_handler(self, ex_cls, ex, traceback): + def sigint_handler(self, ex_cls, ex, tb): """Handler for the SIGINT signal.""" + logger.info("sigint_handler") if ex_cls == KeyboardInterrupt: logger.info("found KeyboardInterrupt") self.unsubscribe() QtGui.QApplication.exit() else: - logger.critical(''.join(traceback.format_tb(traceback))) + logger.critical(''.join(traceback.format_tb(tb))) logger.critical('{0}: {1}'.format(ex_cls, ex)) def sigterm_handler(self, *args): + logger.info("sigterm_handler") self.unsubscribe() QtGui.QApplication.exit() diff --git a/texter/texter/edit_dialog.ui b/texter/texter/edit_dialog.ui index 64b71c4..4c5c3a9 100644 --- a/texter/texter/edit_dialog.ui +++ b/texter/texter/edit_dialog.ui @@ -17,7 +17,7 @@ - + diff --git a/texter/texter/edit_dialog_ui.py b/texter/texter/edit_dialog_ui.py index 9dfbd4f..6760f15 100644 --- a/texter/texter/edit_dialog_ui.py +++ b/texter/texter/edit_dialog_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'edit_dialog.ui' # -# Created: Sat May 17 16:15:38 2014 +# Created: Tue May 27 18:18:57 2014 # by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -31,7 +31,7 @@ class Ui_EditDialog(object): self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.horizontalLayout_2 = QtGui.QHBoxLayout() self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) - self.text_list = QtGui.QTableView(EditDialog) + self.text_list = QtGui.QListView(EditDialog) self.text_list.setObjectName(_fromUtf8("text_list")) self.horizontalLayout_2.addWidget(self.text_list) self.text_preview = QtGui.QTextEdit(EditDialog) diff --git a/texter/texter/main.py b/texter/texter/main.py index 923642c..c084596 100644 --- a/texter/texter/main.py +++ b/texter/texter/main.py @@ -25,6 +25,7 @@ import cPickle import os.path import re import sys +import traceback from PyQt4 import QtCore, QtGui @@ -123,7 +124,7 @@ class EditDialog(QtGui.QWidget, Ui_EditDialog): def slot_show_text(self, model_index): try: - self.text_preview.setTextOrHtml(self.parent().parent().model.text_db[model_index.row()][1]) + self.text_preview.setHtml(self.parent().parent().model.text_db[model_index.row()][1]) except IndexError: pass @@ -232,10 +233,12 @@ class TextAnimation(QtCore.QObject): self.count += 1 -class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, PsyQtClientBase): +class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface): def __init__(self, args, parent=None): self.args = args - super(MainWindow, self).__init__() + #super(MainWindow, self).__init__() + #PsyQtClientBase.__init__(self) + KMainWindow.__init__(self, parent) self.is_streaming = False self.live_center_action = None @@ -257,12 +260,11 @@ class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, Ps self.dialog = None self.current_object = None self.current_index = -1 - self.win_id = self.winId() self.is_auto_publish = False self.setupUi(self) - self.coords = self.live_text_rect() + self.win_id = self.live_text.winId() self.fps = 12.5 self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self, self.fps) @@ -307,6 +309,9 @@ class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, Ps self.start_streaming() self.show() + timer = QtCore.QTimer() + timer.start(2000) + timer.timeout.connect(lambda: None) def pubdir(self): return os.path.dirname(os.path.abspath(__file__)) @@ -317,9 +322,10 @@ class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, Ps global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight())) return global_rect.x(), global_rect.y() - def render_image(self): - pixmap = QPixmap.grabWindow(self.win_id, self.coords.x() + 10, self.coords.y() + 10, 768, 576) + public_rect = self.live_text_rect() + #global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight())) + pixmap = QPixmap.grabWindow(self.win_id, public_rect.x() + 1, public_rect.y() + 1, 768, 576) buf = QBuffer() buf.open(QIODevice.WriteOnly) pixmap.save(buf, "JPG", 75) @@ -505,6 +511,7 @@ class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, Ps self.streaming_action.setChecked(True) def closeEvent(self, event): + logger.info("closeEvent") if self.db_dirty: self.dialog = KDialog(self) self.dialog.setCaption("4.48 texter - text db not saved") @@ -513,7 +520,7 @@ class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, Ps self.dialog.setButtons(KDialog.ButtonCodes(KDialog.Ok | KDialog.Cancel)) self.dialog.okClicked.connect(self.slot_save) self.dialog.exec_() - super(self, MainWindow).closeEvent(event) + event.accept() def live_text_rect(self): return self.live_text.geometry() @@ -719,7 +726,7 @@ class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, Ps return try: - self.model.text_db = [list(i) for i in cPickle.load(db_file)] + self.model.text_db = [list(i) for i in cPickle.load(db_file)] except ValueError, error: logger.exception(error) @@ -727,6 +734,15 @@ class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, Ps self.text_combo.setCurrentItem(0) self.slot_load_preview_text(0) + def sigint_handler(self, ex_cls, ex, tb): + """Handler for the SIGINT signal.""" + if ex_cls == KeyboardInterrupt: + logger.info("found KeyboardInterrupt") + QtGui.QApplication.exit() + else: + logger.critical(''.join(traceback.format_tb(tb))) + logger.critical('{0}: {1}'.format(ex_cls, ex)) + def main(): arg_parser = ArgParser("texter") From 28036e620575cb2f963661d3d9f7b2409c97421d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Sat, 7 Jun 2014 11:03:20 +0200 Subject: [PATCH 7/7] new version --- dump_grabber/dump_grabber/main.py | 116 ++--- dump_grabber/setup.py | 7 +- ekgplotter/distribute_setup.py | 556 ++++++++++++++++++++++++ ekgplotter/ekgplotter/main_qt.py | 153 ++++--- ekgplotter/setup.py | 7 +- psylib/distribute_setup.py | 556 ++++++++++++++++++++++++ psylib/psylib/mjpeg_streaming_server.py | 53 ++- psylib/setup.py | 5 +- sensors2osc/distribute_setup.py | 556 ++++++++++++++++++++++++ texter/distribute_setup.py | 556 ++++++++++++++++++++++++ texter/setup.py | 7 +- 11 files changed, 2409 insertions(+), 163 deletions(-) create mode 100644 ekgplotter/distribute_setup.py create mode 100644 psylib/distribute_setup.py create mode 100644 sensors2osc/distribute_setup.py create mode 100644 texter/distribute_setup.py diff --git a/dump_grabber/dump_grabber/main.py b/dump_grabber/dump_grabber/main.py index 396da28..5dd5951 100644 --- a/dump_grabber/dump_grabber/main.py +++ b/dump_grabber/dump_grabber/main.py @@ -26,28 +26,33 @@ import re import signal import sys from collections import deque -from datetime import datetime -from chaosc.argparser_groups import * + +from chaosc.argparser_groups import ArgParser from chaosc.lib import logger, resolve_host from PyQt4 import QtCore, QtGui from PyQt4.QtCore import QBuffer, QByteArray, QIODevice -from PyQt4.QtNetwork import QTcpServer, QTcpSocket, QUdpSocket, QHostAddress +from PyQt4.QtGui import QPixmap, QPainter +from PyQt4.QtNetwork import QHostAddress from dump_grabber.dump_grabber_ui import Ui_MainWindow -from psylib.mjpeg_streaming_server import * +from psylib.mjpeg_streaming_server import (MjpegStreamingServer, + MjpegStreamingConsumerInterface) from psylib.psyqt_base import PsyQtChaoscClientBase try: from chaosc.c_osc_lib import OSCMessage, decode_osc -except ImportError as e: +except ImportError: from chaosc.osc_lib import OSCMessage, decode_osc -app = QtGui.QApplication([]) +QTAPP = QtGui.QApplication([]) class ExclusiveTextStorage(object): + """Stores the text representation of per actor osc messages""" + def __init__(self, columns, default_font, column_width, line_height, scene): self.column_count = columns - self.colors = (QtCore.Qt.red, QtCore.Qt.green, QtGui.QColor(46, 100, 254)) + self.colors = ( + QtCore.Qt.red, QtCore.Qt.green, QtGui.QColor(46, 100, 254)) self.lines = deque() self.default_font = default_font self.column_width = column_width @@ -58,67 +63,67 @@ class ExclusiveTextStorage(object): def init_columns(self): color = self.colors[0] - for y in range(self.num_lines): + for line_index in range(self.num_lines): text_item = self.graphics_scene.addSimpleText("", self.default_font) text_item.setBrush(color) - text_item.setPos(0, y * self.line_height) + text_item.setPos(0, line_index * self.line_height) self.lines.append(text_item) def __add_text(self, column, text): text_item = self.graphics_scene.addSimpleText(text, self.default_font) text_item.setX(column * self.column_width) - color = self.colors[column] - text_item.setBrush(color) - + text_item.setBrush(self.colors[column]) old_item = self.lines.popleft() self.graphics_scene.removeItem(old_item) self.lines.append(text_item) def finish(self): - while 1: - try: - column, text = self.data.popleft() - self.__add_text(column, text) - except IndexError, msg: - break + for column, text in self.data: + self.__add_text(column, text) + self.data.clear() - - for iy, text_item in enumerate(self.lines): - text_item.setY(iy * self.line_height) + for text_index, text_item in enumerate(self.lines): + text_item.setY(text_index * self.line_height) def add_text(self, column, text): self.data.append((column, text)) -class MainWindow(QtGui.QMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, PsyQtChaoscClientBase): +class MainWindow(QtGui.QMainWindow, Ui_MainWindow, + MjpegStreamingConsumerInterface, PsyQtChaoscClientBase): + + """This app receives per actor osc messages and provides an mjpeg stream + with colored text representation arranged in columns""" + def __init__(self, args, parent=None): self.args = args - #PsyQtChaoscClientBase.__init__(self) super(MainWindow, self).__init__() - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.setupUi(self) - self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self) + self.http_server = MjpegStreamingServer( + (args.http_host, args.http_port), self) self.http_server.listen(port=args.http_port) - self.graphics_view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.graphics_view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.graphics_view.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarAlwaysOff) + self.graphics_view.setVerticalScrollBarPolicy( + QtCore.Qt.ScrollBarAlwaysOff) self.graphics_view.setRenderHint(QtGui.QPainter.Antialiasing, True) self.graphics_view.setFrameStyle(QtGui.QFrame.NoFrame) self.graphics_scene = QtGui.QGraphicsScene(self) - self.graphics_scene.setSceneRect(0,0, 775, 580) + self.graphics_scene.setSceneRect(0, 0, 775, 580) self.graphics_view.setScene(self.graphics_scene) self.default_font = QtGui.QFont("Monospace", 14) self.default_font.setStyleHint(QtGui.QFont.Monospace) - #self.default_font.setBold(True) + self.default_font.setBold(True) self.graphics_scene.setFont(self.default_font) self.font_metrics = QtGui.QFontMetrics(self.default_font) self.line_height = self.font_metrics.height() columns = 3 self.column_width = 775 / columns - - self.text_storage = ExclusiveTextStorage(columns, self.default_font, self.column_width, self.line_height, self.graphics_scene) + self.text_storage = ExclusiveTextStorage(columns, self.default_font, + self.column_width, + self.line_height, + self.graphics_scene) self.text_storage.init_columns() - self.regex = re.compile("^/(uwe|merle|bjoern)/(.*?)$") def pubdir(self): @@ -129,7 +134,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterfa msg.appendTypedArg("localhost", "s") msg.appendTypedArg(self.args.client_port, "i") msg.appendTypedArg(self.args.authenticate, "s") - self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110) + self.osc_sock.writeDatagram( + QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110) def handle_osc_error(self, error): logger.info("osc socket error %d", error) @@ -139,28 +145,29 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterfa def render_image(self): self.text_storage.finish() - image = QtGui.QImage(768, 576, QtGui.QImage.Format_ARGB32_Premultiplied) + image = QPixmap(768, 576) image.fill(QtCore.Qt.black) - painter = QtGui.QPainter(image) - painter.setRenderHints(QtGui.QPainter.RenderHint( - QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing), - True) + painter = QPainter(image) + painter.setRenderHints(QPainter.RenderHint( + QPainter.Antialiasing | QPainter.TextAntialiasing), True) painter.setFont(self.default_font) - self.graphics_view.render(painter, target=QtCore.QRectF(0, 0, 768, 576), + self.graphics_view.render( + painter, target=QtCore.QRectF(0, 0, 768, 576), source=QtCore.QRect(0, 0, 768, 576)) painter.end() buf = QBuffer() buf.open(QIODevice.WriteOnly) - image.save(buf, "JPG", 85) + image.save(buf, "JPG", 100) image_data = buf.data() return image_data def got_message(self): while self.osc_sock.hasPendingDatagrams(): - data, address, port = self.osc_sock.readDatagram(self.osc_sock.pendingDatagramSize()) + data, address, port = self.osc_sock.readDatagram( + self.osc_sock.pendingDatagramSize()) try: osc_address, typetags, args = decode_osc(data, 0, len(data)) - except Exception: + except ValueError: return try: actor, text = self.regex.match(osc_address).groups() @@ -170,11 +177,14 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterfa if text == "temperatur": text += "e" if actor == "merle": - self.add_text(0, "%s = %s" % (text, ", ".join([str(i) for i in args]))) + self.add_text(0, "%s = %s" % ( + text, ", ".join([str(i) for i in args]))) elif actor == "uwe": - self.add_text(1, "%s = %s" % (text, ", ".join([str(i) for i in args]))) + self.add_text(1, "%s = %s" % ( + text, ", ".join([str(i) for i in args]))) elif actor == "bjoern": - self.add_text(2, "%s = %s" % (text, ", ".join([str(i) for i in args]))) + self.add_text(2, "%s = %s" % ( + text, ", ".join([str(i) for i in args]))) return True @@ -183,21 +193,23 @@ def main(): arg_parser.add_global_group() client_group = arg_parser.add_client_group() arg_parser.add_argument(client_group, '-x', "--http_host", default="::", - help='my host, defaults to "::"') + help='my host, defaults to "::"') arg_parser.add_argument(client_group, '-X', "--http_port", default=9001, - type=int, help='my port, defaults to 9001') + type=int, help='my port, defaults to 9001') arg_parser.add_chaosc_group() arg_parser.add_subscriber_group() args = arg_parser.finalize() - http_host, http_port = resolve_host(args.http_host, args.http_port, args.address_family) - args.chaosc_host, args.chaosc_port = resolve_host(args.chaosc_host, args.chaosc_port, args.address_family) + args.http_host, args.http_port = resolve_host( + args.http_host, args.http_port, args.address_family) + args.chaosc_host, args.chaosc_port = resolve_host( + args.chaosc_host, args.chaosc_port, args.address_family) window = MainWindow(args) sys.excepthook = window.sigint_handler signal.signal(signal.SIGTERM, window.sigterm_handler) - app.exec_() + QTAPP.exec_() -if ( __name__ == '__main__' ): +if __name__ == '__main__': main() diff --git a/dump_grabber/setup.py b/dump_grabber/setup.py index 48cf6f7..39c5f87 100644 --- a/dump_grabber/setup.py +++ b/dump_grabber/setup.py @@ -1,9 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from distribute_setup import use_setuptools -use_setuptools() - import sys from setuptools import find_packages, setup @@ -12,13 +9,15 @@ if sys.version_info >= (3,): setup( name='dump_grabber', - version="0.1", + version="0.2", packages=find_packages(exclude=["scripts",]), include_package_data = True, exclude_package_data = {'': ['.gitignore']}, + install_requires = ["psylib"], + # installing unzipped zip_safe = False, diff --git a/ekgplotter/distribute_setup.py b/ekgplotter/distribute_setup.py new file mode 100644 index 0000000..3553b21 --- /dev/null +++ b/ekgplotter/distribute_setup.py @@ -0,0 +1,556 @@ +#!python +"""Bootstrap distribute installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from distribute_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import shutil +import sys +import time +import fnmatch +import tempfile +import tarfile +import optparse + +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +try: + import subprocess + + def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +except ImportError: + # will be used for python 2.3 + def _python_cmd(*args): + args = (sys.executable,) + args + # quoting arguments if windows + if sys.platform == 'win32': + def quote(arg): + if ' ' in arg: + return '"%s"' % arg + return arg + args = [quote(arg) for arg in args] + return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 + +DEFAULT_VERSION = "0.6.49" +DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" +SETUPTOOLS_FAKED_VERSION = "0.6c11" + +SETUPTOOLS_PKG_INFO = """\ +Metadata-Version: 1.0 +Name: setuptools +Version: %s +Summary: xxxx +Home-page: xxx +Author: xxx +Author-email: xxx +License: xxx +Description: xxx +""" % SETUPTOOLS_FAKED_VERSION + + +def _install(tarball, install_args=()): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # installing + log.warn('Installing Distribute') + if not _python_cmd('setup.py', 'install', *install_args): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + # exitcode will be 2 + return 2 + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + + +def _build_egg(egg, tarball, to_dir): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # building an egg + log.warn('Building a Distribute egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) + sys.path.insert(0, egg) + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15, no_fake=True): + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules + try: + try: + import pkg_resources + + # Setuptools 0.7b and later is a suitable (and preferable) + # substitute for any Distribute version. + try: + pkg_resources.require("setuptools>=0.7b") + return + except (pkg_resources.DistributionNotFound, + pkg_resources.VersionConflict): + pass + + if not hasattr(pkg_resources, '_distribute'): + if not no_fake: + _fake_setuptools() + raise ImportError + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("distribute>=" + version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of distribute (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U distribute'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) + finally: + if not no_fake: + _create_fake_setuptools_pkg_info(to_dir) + + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15): + """Download distribute from a specified location and return its filename + + `version` should be a valid distribute version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + try: + from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen + tgz_name = "distribute-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + log.warn("Downloading %s", url) + src = urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = src.read() + dst = open(saveto, "wb") + dst.write(data) + finally: + if src: + src.close() + if dst: + dst.close() + return os.path.realpath(saveto) + + +def _no_sandbox(function): + def __no_sandbox(*args, **kw): + try: + from setuptools.sandbox import DirectorySandbox + if not hasattr(DirectorySandbox, '_old'): + def violation(*args): + pass + DirectorySandbox._old = DirectorySandbox._violation + DirectorySandbox._violation = violation + patched = True + else: + patched = False + except ImportError: + patched = False + + try: + return function(*args, **kw) + finally: + if patched: + DirectorySandbox._violation = DirectorySandbox._old + del DirectorySandbox._old + + return __no_sandbox + + +def _patch_file(path, content): + """Will backup the file then patch it""" + f = open(path) + existing_content = f.read() + f.close() + if existing_content == content: + # already patched + log.warn('Already patched.') + return False + log.warn('Patching...') + _rename_path(path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + return True + +_patch_file = _no_sandbox(_patch_file) + + +def _same_content(path, content): + f = open(path) + existing_content = f.read() + f.close() + return existing_content == content + + +def _rename_path(path): + new_name = path + '.OLD.%s' % time.time() + log.warn('Renaming %s to %s', path, new_name) + os.rename(path, new_name) + return new_name + + +def _remove_flat_installation(placeholder): + if not os.path.isdir(placeholder): + log.warn('Unkown installation at %s', placeholder) + return False + found = False + for file in os.listdir(placeholder): + if fnmatch.fnmatch(file, 'setuptools*.egg-info'): + found = True + break + if not found: + log.warn('Could not locate setuptools*.egg-info') + return + + log.warn('Moving elements out of the way...') + pkg_info = os.path.join(placeholder, file) + if os.path.isdir(pkg_info): + patched = _patch_egg_dir(pkg_info) + else: + patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) + + if not patched: + log.warn('%s already patched.', pkg_info) + return False + # now let's move the files out of the way + for element in ('setuptools', 'pkg_resources.py', 'site.py'): + element = os.path.join(placeholder, element) + if os.path.exists(element): + _rename_path(element) + else: + log.warn('Could not find the %s element of the ' + 'Setuptools distribution', element) + return True + +_remove_flat_installation = _no_sandbox(_remove_flat_installation) + + +def _after_install(dist): + log.warn('After install bootstrap.') + placeholder = dist.get_command_obj('install').install_purelib + _create_fake_setuptools_pkg_info(placeholder) + + +def _create_fake_setuptools_pkg_info(placeholder): + if not placeholder or not os.path.exists(placeholder): + log.warn('Could not find the install location') + return + pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ + (SETUPTOOLS_FAKED_VERSION, pyver) + pkg_info = os.path.join(placeholder, setuptools_file) + if os.path.exists(pkg_info): + log.warn('%s already exists', pkg_info) + return + + log.warn('Creating %s', pkg_info) + try: + f = open(pkg_info, 'w') + except EnvironmentError: + log.warn("Don't have permissions to write %s, skipping", pkg_info) + return + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + + pth_file = os.path.join(placeholder, 'setuptools.pth') + log.warn('Creating %s', pth_file) + f = open(pth_file, 'w') + try: + f.write(os.path.join(os.curdir, setuptools_file)) + finally: + f.close() + +_create_fake_setuptools_pkg_info = _no_sandbox( + _create_fake_setuptools_pkg_info +) + + +def _patch_egg_dir(path): + # let's check if it's already patched + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + if os.path.exists(pkg_info): + if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): + log.warn('%s already patched.', pkg_info) + return False + _rename_path(path) + os.mkdir(path) + os.mkdir(os.path.join(path, 'EGG-INFO')) + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + return True + +_patch_egg_dir = _no_sandbox(_patch_egg_dir) + + +def _before_install(): + log.warn('Before install bootstrap.') + _fake_setuptools() + + +def _under_prefix(location): + if 'install' not in sys.argv: + return True + args = sys.argv[sys.argv.index('install') + 1:] + for index, arg in enumerate(args): + for option in ('--root', '--prefix'): + if arg.startswith('%s=' % option): + top_dir = arg.split('root=')[-1] + return location.startswith(top_dir) + elif arg == option: + if len(args) > index: + top_dir = args[index + 1] + return location.startswith(top_dir) + if arg == '--user' and USER_SITE is not None: + return location.startswith(USER_SITE) + return True + + +def _fake_setuptools(): + log.warn('Scanning installed packages') + try: + import pkg_resources + except ImportError: + # we're cool + log.warn('Setuptools or Distribute does not seem to be installed.') + return + ws = pkg_resources.working_set + try: + setuptools_dist = ws.find( + pkg_resources.Requirement.parse('setuptools', replacement=False) + ) + except TypeError: + # old distribute API + setuptools_dist = ws.find( + pkg_resources.Requirement.parse('setuptools') + ) + + if setuptools_dist is None: + log.warn('No setuptools distribution found') + return + # detecting if it was already faked + setuptools_location = setuptools_dist.location + log.warn('Setuptools installation detected at %s', setuptools_location) + + # if --root or --preix was provided, and if + # setuptools is not located in them, we don't patch it + if not _under_prefix(setuptools_location): + log.warn('Not patching, --root or --prefix is installing Distribute' + ' in another location') + return + + # let's see if its an egg + if not setuptools_location.endswith('.egg'): + log.warn('Non-egg installation') + res = _remove_flat_installation(setuptools_location) + if not res: + return + else: + log.warn('Egg installation') + pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') + if (os.path.exists(pkg_info) and + _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): + log.warn('Already patched.') + return + log.warn('Patching...') + # let's create a fake egg replacing setuptools one + res = _patch_egg_dir(setuptools_location) + if not res: + return + log.warn('Patching complete.') + _relaunch() + + +def _relaunch(): + log.warn('Relaunching...') + # we have to relaunch the process + # pip marker to avoid a relaunch bug + _cmd1 = ['-c', 'install', '--single-version-externally-managed'] + _cmd2 = ['-c', 'install', '--record'] + if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: + sys.argv[0] = 'setup.py' + args = [sys.executable] + sys.argv + sys.exit(subprocess.call(args)) + + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + +def _build_install_args(options): + """ + Build the arguments to 'python setup.py install' on the distribute package + """ + install_args = [] + if options.user_install: + if sys.version_info < (2, 6): + log.warn("--user requires Python 2.6 or later") + raise SystemExit(1) + install_args.append('--user') + return install_args + +def _parse_args(): + """ + Parse the command line for options + """ + parser = optparse.OptionParser() + parser.add_option( + '--user', dest='user_install', action='store_true', default=False, + help='install in user site package (requires Python 2.6 or later)') + parser.add_option( + '--download-base', dest='download_base', metavar="URL", + default=DEFAULT_URL, + help='alternative URL from where to download the distribute package') + options, args = parser.parse_args() + # positional arguments are ignored + return options + +def main(version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + options = _parse_args() + tarball = download_setuptools(download_base=options.download_base) + return _install(tarball, _build_install_args(options)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/ekgplotter/ekgplotter/main_qt.py b/ekgplotter/ekgplotter/main_qt.py index 5f75b32..7b40170 100644 --- a/ekgplotter/ekgplotter/main_qt.py +++ b/ekgplotter/ekgplotter/main_qt.py @@ -1,30 +1,25 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# This file is part of sensors2osc package +# This file is part of psychose/ekgplotter package # -# sensors2osc is free software: you can redistribute it and/or modify +# psychose/ekgplotter is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# sensors2osc is distributed in the hope that it will be useful, +# psychose/ekgplotter is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with sensors2osc. If not, see . -# -# found the mjpeg part here, thanks for the nice code :) -# http://hardsoftlucid.wordpress.com/2013/04/11/mjpeg-server-for-webcam-in-python-with-opencv/ -# the osc integration stuff is implemented by me +# along with psychose/ekgplotter. If not, see . # # Copyright (C) 2014 Stefan Kögl from __future__ import absolute_import -import atexit import random import os.path import re @@ -33,13 +28,15 @@ import sys import exceptions from PyQt4 import QtCore, QtGui -from PyQt4.QtCore import QBuffer, QByteArray, QIODevice -from PyQt4.QtNetwork import QUdpSocket, QHostAddress +from PyQt4.QtGui import QImage, QPixmap, QMainWindow +from PyQt4.QtCore import QBuffer, QIODevice import numpy as np import pyqtgraph as pg from pyqtgraph.widgets.PlotWidget import PlotWidget +from pyqtgraph.graphicsItems.PlotCurveItem import PlotCurveItem +from pyqtgraph.graphicsItems.ScatterPlotItem import ScatterPlotItem from chaosc.argparser_groups import ArgParser from chaosc.lib import logger, resolve_host @@ -47,9 +44,9 @@ from psylib.mjpeg_streaming_server import * from psylib.psyqt_base import PsyQtChaoscClientBase try: - from chaosc.c_osc_lib import OSCMessage, decode_osc + from chaosc.c_osc_lib import decode_osc except ImportError as e: - from chaosc.osc_lib import OSCMessage, decode_osc + from chaosc.osc_lib import decode_osc qtapp = QtGui.QApplication([]) @@ -67,8 +64,8 @@ class Generator(object): self.count = 0 self.pulse = random.randint(85, 105) self.delta = delta - self.multiplier = 4 - self.steps, _ = get_steps(self.pulse, delta / self.multiplier) + self.finished = False + self.steps, _ = get_steps(self.pulse, delta / 4) def __call__(self): while 1: @@ -89,6 +86,7 @@ class Generator(object): elif self.count < self.steps: value = random.randint(15, 30) else: + self.finished = True self.count = 0 value = 30 @@ -97,8 +95,7 @@ class Generator(object): def set_pulse(self, pulse): self.pulse = pulse - self.steps, _ = get_steps(pulse, self.delta / self.multiplier) - + self.steps, _ = get_steps(pulse, self.delta) def retrigger(self): self.count = self.steps / 2 @@ -112,37 +109,33 @@ class Actor(object): self.ix = ix self.max_actors = max_actors self.actor_height = actor_height - self.updated = 0 - self.offset = ix * actor_height - self.data = np.array([self.offset] * num_data) + self.data = np.array([self.offset + 30] * num_data) self.head = 0 self.pre_head = 0 - self.plotItem = pg.PlotCurveItem(pen=pg.mkPen(color, width=3), width=4, name=name) - #self.plotItem.setShadowPen(pg.mkPen("w", width=5)) - self.plotPoint = pg.ScatterPlotItem(pen=pg.mkPen("w", width=5), brush=pg.mkBrush(color), size=5) + self.plotItem = PlotCurveItem(pen=pg.mkPen(color, width=3), width=4, name=name) + self.plotPoint = ScatterPlotItem(pen=pg.mkPen("w", width=5), brush=pg.mkBrush(color), size=5) self.osci = None self.osci_obj = None + self.render() def __str__(self): return "" % (self.name, self.head) - __repr__ = __str__ - + def __repr__(self): + return "Actor(%r, %r, %r, %r, %r, %r)" % (self.name, self.num_data, + self.color, self.ix, self.max_actors, self.actor_height) def add_value(self, value): - dp = self.head - self.data[dp] = value / self.max_actors + self.offset - self.pre_head = dp - self.head = (dp + 1) % self.num_data - self.updated += 1 + self.pre_head = self.head + self.data[self.pre_head] = value / self.max_actors + self.offset + self.head = (self.pre_head + 1) % self.num_data def fill_missing(self, count): dp = self.head for i in range(count): self.data[dp] = self.offset dp = (dp + 1) % self.num_data - self.updated += 1 self.pre_head = (dp - 1) % self.num_data self.head = dp @@ -151,41 +144,47 @@ class Actor(object): self.plotItem.setData(y=self.data, clear=True) self.plotPoint.setData(x=[self.pre_head], y=[self.data[self.pre_head]]) +class PlotWindow(PlotWidget): + def __init__(self, title=None, **kargs): + self.win = QtGui.QMainWindow() + self.win.resize(768, 576) + PlotWidget.__init__(self, **kargs) + self.win.setCentralWidget(self) + for m in ['resize']: + setattr(self, m, getattr(self.win, m)) + if title is not None: + self.win.setWindowTitle(title) + self.win.show() -class EkgPlotWidget(PlotWidget, PsyQtChaoscClientBase, MjpegStreamingConsumerInterface): + +class EkgPlotWidget(QMainWindow, PsyQtChaoscClientBase, MjpegStreamingConsumerInterface): def __init__(self, args, parent=None): self.args = args super(EkgPlotWidget, self).__init__() PsyQtChaoscClientBase.__init__(self) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - - self.fps = 12.5 - self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self, self.fps) - self.http_server.listen(port=args.http_port) - - self.num_data = 100 - - self.hide() - self.showGrid(False, False) - self.setYRange(0, 255) - self.setXRange(0, self.num_data) - self.resize(768, 576) + self.plot_widget = PlotWidget(title="Psychose - EkgPlotter") colors = ["r", "g", "b"] - - ba = self.getAxis("bottom") - bl = self.getAxis("left") - ba.setTicks([]) - bl.setTicks([]) - ba.hide() - bl.hide() self.active_actors = list() - self.actors = dict() - self.max_value = 255 actor_names = ["merle", "uwe", "bjoern"] self.max_actors = len(actor_names) self.actor_height = self.max_value / self.max_actors + self.fps = 12.5 + self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self, self.fps) + self.http_server.listen(port=args.http_port) + self.num_data = 100 + self.plot_widget.showGrid(False, False) + self.plot_widget.setYRange(0, 255) + self.plot_widget.setXRange(0, self.num_data) + self.plot_widget.resize(768, 576) + + bottom_axis = self.plot_widget.getAxis("bottom") + left_axis = self.plot_widget.getAxis("left") + bottom_axis.setTicks([]) + left_axis.setTicks([]) + bottom_axis.hide() + left_axis.hide() for ix, (actor_name, color) in enumerate(zip(actor_names, colors)): self.add_actor(actor_name, self.num_data, color, ix, self.max_actors, self.actor_height) @@ -193,21 +192,16 @@ class EkgPlotWidget(PlotWidget, PsyQtChaoscClientBase, MjpegStreamingConsumerInt self.set_positions() self.heartbeat_regex = re.compile("^/(.*?)/heartbeat$") - self.pull_timer = QtCore.QTimer() - self.pull_timer.timeout.connect(self.slot_pull_ekg) - self.pull_timer.start(40) - def pubdir(self): return os.path.dirname(os.path.abspath(__file__)) - def add_actor(self, actor_name, num_data, color, ix, max_actors, actor_height): actor_obj = Actor(actor_name, num_data, color, ix, max_actors, actor_height) self.actors[actor_name] = actor_obj - self.addItem(actor_obj.plotItem) - self.addItem(actor_obj.plotPoint) + self.plot_widget.addItem(actor_obj.plotItem) + self.plot_widget.addItem(actor_obj.plotPoint) self.active_actors.append(actor_obj) - actor_obj.osci_obj = Generator(delta=self.http_server.timer_delta) + actor_obj.osci_obj = Generator(pulse=random.randint(88, 104), delta=self.http_server.timer_delta) actor_obj.osci = actor_obj.osci_obj() def set_positions(self): @@ -219,7 +213,6 @@ class EkgPlotWidget(PlotWidget, PsyQtChaoscClientBase, MjpegStreamingConsumerInt return self.max_actors def update(self, osc_address, args): - res = self.heartbeat_regex.match(osc_address) if res: actor_name = res.group(1) @@ -229,16 +222,25 @@ class EkgPlotWidget(PlotWidget, PsyQtChaoscClientBase, MjpegStreamingConsumerInt actor_obj.osci_obj.retrigger() actor_obj.osci_obj.set_pulse(args[1]) - def render_image(self): for actor_obj in self.active_actors: + actor_obj.add_value(actor_obj.osci.next()) + actor_obj.add_value(actor_obj.osci.next()) + actor_obj.add_value(actor_obj.osci.next()) + actor_obj.add_value(actor_obj.osci.next()) actor_obj.render() - exporter = pg.exporters.ImageExporter.ImageExporter(self.plotItem) - exporter.parameters()['width'] = 768 - img = exporter.export(toBytes=True) + image = QPixmap(768, 576) + image.fill(QtCore.Qt.white) + painter = QtGui.QPainter(image) + painter.setRenderHints(QtGui.QPainter.RenderHint( + QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing), + True) + scene = self.plot_widget.plotItem.scene() + scene.render(painter, QtCore.QRectF(0, 0, 768, 576), QtCore.QRectF(0, 0, 768, 576)) + painter.end() buf = QBuffer() buf.open(QIODevice.WriteOnly) - img.save(buf, "JPG", 75) + image.save(buf, "JPG", 80) JpegData = buf.data() return JpegData @@ -247,16 +249,11 @@ class EkgPlotWidget(PlotWidget, PsyQtChaoscClientBase, MjpegStreamingConsumerInt data, address, port = self.osc_sock.readDatagram(self.osc_sock.pendingDatagramSize()) try: osc_address, typetags, args = decode_osc(data, 0, len(data)) - except Exception, e: - logger.exception(e) + except ValueError, error: + logger.exception(error) else: self.update(osc_address, args) - def slot_pull_ekg(self): - for actor_obj in self.active_actors: - actor_obj.add_value(actor_obj.osci.next()) - - def main(): arg_parser = ArgParser("ekgplotter") @@ -274,8 +271,10 @@ def main(): args.chaosc_host, args.chaosc_port = resolve_host(args.chaosc_host, args.chaosc_port, args.address_family) window = EkgPlotWidget(args) - sys.excepthook = window.sigint_handler - signal.signal(signal.SIGTERM, window.sigterm_handler) + logger.info("foooooooo") + window.hide() + #sys.excepthook = window.sigint_handler + #signal.signal(signal.SIGTERM, window.sigterm_handler) qtapp.exec_() diff --git a/ekgplotter/setup.py b/ekgplotter/setup.py index 85ac4de..180b61e 100644 --- a/ekgplotter/setup.py +++ b/ekgplotter/setup.py @@ -1,9 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from distribute_setup import use_setuptools -use_setuptools() - import sys from setuptools import find_packages, setup @@ -12,7 +9,7 @@ if sys.version_info >= (3,): setup( name='ekgplotter', - version="0.1", + version="0.2", packages=find_packages(exclude=["scripts",]), include_package_data = True, @@ -22,7 +19,7 @@ setup( exclude_package_data = {'': ['.gitignore']}, - install_requires=["pyqtgraph"], + install_requires=["psylib", "pyqtgraph"], # installing unzipped zip_safe = False, diff --git a/psylib/distribute_setup.py b/psylib/distribute_setup.py new file mode 100644 index 0000000..3553b21 --- /dev/null +++ b/psylib/distribute_setup.py @@ -0,0 +1,556 @@ +#!python +"""Bootstrap distribute installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from distribute_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import shutil +import sys +import time +import fnmatch +import tempfile +import tarfile +import optparse + +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +try: + import subprocess + + def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +except ImportError: + # will be used for python 2.3 + def _python_cmd(*args): + args = (sys.executable,) + args + # quoting arguments if windows + if sys.platform == 'win32': + def quote(arg): + if ' ' in arg: + return '"%s"' % arg + return arg + args = [quote(arg) for arg in args] + return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 + +DEFAULT_VERSION = "0.6.49" +DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" +SETUPTOOLS_FAKED_VERSION = "0.6c11" + +SETUPTOOLS_PKG_INFO = """\ +Metadata-Version: 1.0 +Name: setuptools +Version: %s +Summary: xxxx +Home-page: xxx +Author: xxx +Author-email: xxx +License: xxx +Description: xxx +""" % SETUPTOOLS_FAKED_VERSION + + +def _install(tarball, install_args=()): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # installing + log.warn('Installing Distribute') + if not _python_cmd('setup.py', 'install', *install_args): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + # exitcode will be 2 + return 2 + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + + +def _build_egg(egg, tarball, to_dir): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # building an egg + log.warn('Building a Distribute egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) + sys.path.insert(0, egg) + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15, no_fake=True): + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules + try: + try: + import pkg_resources + + # Setuptools 0.7b and later is a suitable (and preferable) + # substitute for any Distribute version. + try: + pkg_resources.require("setuptools>=0.7b") + return + except (pkg_resources.DistributionNotFound, + pkg_resources.VersionConflict): + pass + + if not hasattr(pkg_resources, '_distribute'): + if not no_fake: + _fake_setuptools() + raise ImportError + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("distribute>=" + version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of distribute (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U distribute'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) + finally: + if not no_fake: + _create_fake_setuptools_pkg_info(to_dir) + + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15): + """Download distribute from a specified location and return its filename + + `version` should be a valid distribute version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + try: + from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen + tgz_name = "distribute-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + log.warn("Downloading %s", url) + src = urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = src.read() + dst = open(saveto, "wb") + dst.write(data) + finally: + if src: + src.close() + if dst: + dst.close() + return os.path.realpath(saveto) + + +def _no_sandbox(function): + def __no_sandbox(*args, **kw): + try: + from setuptools.sandbox import DirectorySandbox + if not hasattr(DirectorySandbox, '_old'): + def violation(*args): + pass + DirectorySandbox._old = DirectorySandbox._violation + DirectorySandbox._violation = violation + patched = True + else: + patched = False + except ImportError: + patched = False + + try: + return function(*args, **kw) + finally: + if patched: + DirectorySandbox._violation = DirectorySandbox._old + del DirectorySandbox._old + + return __no_sandbox + + +def _patch_file(path, content): + """Will backup the file then patch it""" + f = open(path) + existing_content = f.read() + f.close() + if existing_content == content: + # already patched + log.warn('Already patched.') + return False + log.warn('Patching...') + _rename_path(path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + return True + +_patch_file = _no_sandbox(_patch_file) + + +def _same_content(path, content): + f = open(path) + existing_content = f.read() + f.close() + return existing_content == content + + +def _rename_path(path): + new_name = path + '.OLD.%s' % time.time() + log.warn('Renaming %s to %s', path, new_name) + os.rename(path, new_name) + return new_name + + +def _remove_flat_installation(placeholder): + if not os.path.isdir(placeholder): + log.warn('Unkown installation at %s', placeholder) + return False + found = False + for file in os.listdir(placeholder): + if fnmatch.fnmatch(file, 'setuptools*.egg-info'): + found = True + break + if not found: + log.warn('Could not locate setuptools*.egg-info') + return + + log.warn('Moving elements out of the way...') + pkg_info = os.path.join(placeholder, file) + if os.path.isdir(pkg_info): + patched = _patch_egg_dir(pkg_info) + else: + patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) + + if not patched: + log.warn('%s already patched.', pkg_info) + return False + # now let's move the files out of the way + for element in ('setuptools', 'pkg_resources.py', 'site.py'): + element = os.path.join(placeholder, element) + if os.path.exists(element): + _rename_path(element) + else: + log.warn('Could not find the %s element of the ' + 'Setuptools distribution', element) + return True + +_remove_flat_installation = _no_sandbox(_remove_flat_installation) + + +def _after_install(dist): + log.warn('After install bootstrap.') + placeholder = dist.get_command_obj('install').install_purelib + _create_fake_setuptools_pkg_info(placeholder) + + +def _create_fake_setuptools_pkg_info(placeholder): + if not placeholder or not os.path.exists(placeholder): + log.warn('Could not find the install location') + return + pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ + (SETUPTOOLS_FAKED_VERSION, pyver) + pkg_info = os.path.join(placeholder, setuptools_file) + if os.path.exists(pkg_info): + log.warn('%s already exists', pkg_info) + return + + log.warn('Creating %s', pkg_info) + try: + f = open(pkg_info, 'w') + except EnvironmentError: + log.warn("Don't have permissions to write %s, skipping", pkg_info) + return + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + + pth_file = os.path.join(placeholder, 'setuptools.pth') + log.warn('Creating %s', pth_file) + f = open(pth_file, 'w') + try: + f.write(os.path.join(os.curdir, setuptools_file)) + finally: + f.close() + +_create_fake_setuptools_pkg_info = _no_sandbox( + _create_fake_setuptools_pkg_info +) + + +def _patch_egg_dir(path): + # let's check if it's already patched + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + if os.path.exists(pkg_info): + if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): + log.warn('%s already patched.', pkg_info) + return False + _rename_path(path) + os.mkdir(path) + os.mkdir(os.path.join(path, 'EGG-INFO')) + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + return True + +_patch_egg_dir = _no_sandbox(_patch_egg_dir) + + +def _before_install(): + log.warn('Before install bootstrap.') + _fake_setuptools() + + +def _under_prefix(location): + if 'install' not in sys.argv: + return True + args = sys.argv[sys.argv.index('install') + 1:] + for index, arg in enumerate(args): + for option in ('--root', '--prefix'): + if arg.startswith('%s=' % option): + top_dir = arg.split('root=')[-1] + return location.startswith(top_dir) + elif arg == option: + if len(args) > index: + top_dir = args[index + 1] + return location.startswith(top_dir) + if arg == '--user' and USER_SITE is not None: + return location.startswith(USER_SITE) + return True + + +def _fake_setuptools(): + log.warn('Scanning installed packages') + try: + import pkg_resources + except ImportError: + # we're cool + log.warn('Setuptools or Distribute does not seem to be installed.') + return + ws = pkg_resources.working_set + try: + setuptools_dist = ws.find( + pkg_resources.Requirement.parse('setuptools', replacement=False) + ) + except TypeError: + # old distribute API + setuptools_dist = ws.find( + pkg_resources.Requirement.parse('setuptools') + ) + + if setuptools_dist is None: + log.warn('No setuptools distribution found') + return + # detecting if it was already faked + setuptools_location = setuptools_dist.location + log.warn('Setuptools installation detected at %s', setuptools_location) + + # if --root or --preix was provided, and if + # setuptools is not located in them, we don't patch it + if not _under_prefix(setuptools_location): + log.warn('Not patching, --root or --prefix is installing Distribute' + ' in another location') + return + + # let's see if its an egg + if not setuptools_location.endswith('.egg'): + log.warn('Non-egg installation') + res = _remove_flat_installation(setuptools_location) + if not res: + return + else: + log.warn('Egg installation') + pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') + if (os.path.exists(pkg_info) and + _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): + log.warn('Already patched.') + return + log.warn('Patching...') + # let's create a fake egg replacing setuptools one + res = _patch_egg_dir(setuptools_location) + if not res: + return + log.warn('Patching complete.') + _relaunch() + + +def _relaunch(): + log.warn('Relaunching...') + # we have to relaunch the process + # pip marker to avoid a relaunch bug + _cmd1 = ['-c', 'install', '--single-version-externally-managed'] + _cmd2 = ['-c', 'install', '--record'] + if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: + sys.argv[0] = 'setup.py' + args = [sys.executable] + sys.argv + sys.exit(subprocess.call(args)) + + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + +def _build_install_args(options): + """ + Build the arguments to 'python setup.py install' on the distribute package + """ + install_args = [] + if options.user_install: + if sys.version_info < (2, 6): + log.warn("--user requires Python 2.6 or later") + raise SystemExit(1) + install_args.append('--user') + return install_args + +def _parse_args(): + """ + Parse the command line for options + """ + parser = optparse.OptionParser() + parser.add_option( + '--user', dest='user_install', action='store_true', default=False, + help='install in user site package (requires Python 2.6 or later)') + parser.add_option( + '--download-base', dest='download_base', metavar="URL", + default=DEFAULT_URL, + help='alternative URL from where to download the distribute package') + options, args = parser.parse_args() + # positional arguments are ignored + return options + +def main(version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + options = _parse_args() + tarball = download_setuptools(download_base=options.download_base) + return _install(tarball, _build_install_args(options)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/psylib/psylib/mjpeg_streaming_server.py b/psylib/psylib/mjpeg_streaming_server.py index 2146b3d..e5af4ee 100644 --- a/psylib/psylib/mjpeg_streaming_server.py +++ b/psylib/psylib/mjpeg_streaming_server.py @@ -49,6 +49,11 @@ class MjpegStreamingConsumerInterface(object): raise NotImplementedError() class MjpegStreamingServer(QTcpServer): + """A simple async http class which provides a mjpeg stream and if found, + an index.html file containing the mjpeg stream. + + Parent should implement the interface 'MjpegStreamingConsumerInterface' + """ def __init__(self, server_address, parent=None, fps=12.5): super(MjpegStreamingServer, self).__init__(parent) @@ -73,7 +78,8 @@ class MjpegStreamingServer(QTcpServer): sock = self.sender() sock_id = id(sock) logger.info("handle_request: sock_id=%r", sock_id) - if sock.state() in (QTcpSocket.UnconnectedState, QTcpSocket.ClosingState): + if sock.state() in ( + QTcpSocket.UnconnectedState, QTcpSocket.ClosingState): logger.info("connection closed") self.sockets.remove(sock) sock.deleteLater() @@ -85,7 +91,9 @@ class MjpegStreamingServer(QTcpServer): logger.info("first line: %r", line) try: resource, ext, http_version = self.get_regex.match(line).groups() - logger.info("resource=%r, ext=%r, http_version=%r", resource, ext, http_version) + logger.info( + "resource=%r, ext=%r, http_version=%r", + resource, ext, http_version) except AttributeError: try: host, port = self.host_regex.match(line).groups() @@ -101,24 +109,30 @@ class MjpegStreamingServer(QTcpServer): if ext == "ico": directory = self.widget.pubdir() try: - data = open(os.path.join(directory, "favicon.ico"), "rb").read() + data = open( + os.path.join(directory, "favicon.ico"), "rb").read() except IOError: - logger.error("request not found/handled - sending 404 not found") + logger.error( + "request not found/handled - sending 404 not found") sock.write("HTTP/1.1 404 Not Found\r\n") return else: - sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data)) + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type:' \ + 'image/x-ico\r\n\r\n%s' % data)) elif ext == "html": directory = self.widget.pubdir() try: - data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id + data = open(os.path.join( + directory, "index.html"), "rb").read() % sock_id self.html_map[sock_id] = None except IOError: - logger.error("request not found/handled - sending 404 not found") + logger.error( + "request not found/handled - sending 404 not found") sock.write("HTTP/1.1 404 Not Found\r\n") return else: - sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: text/html;encoding: utf-8\r\n\r\n%s' % data)) + sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type:"\ + "text/html;encoding: utf-8\r\n\r\n%s' % data)) elif ext == "mjpeg": try: _, html_sock_id = resource.split("_", 1) @@ -131,9 +145,12 @@ class MjpegStreamingServer(QTcpServer): if html_sock_id is not None: self.html_map[html_sock_id] = sock self.stream_clients.append(sock) - sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: multipart/x-mixed-replace; boundary=--2342\r\n\r\n')) + sock.write(QByteArray('HTTP/1.1 200 Ok\r\n" \ + "Content-Type: multipart/x-mixed-replace;" \ + "boundary=--2342\r\n\r\n')) else: - logger.error("request not found/handled - sending 404 not found") + logger.error( + "request not found/handled - sending 404 not found") sock.write("HTTP/1.1 404 Not Found\r\n") def slot_remove_connection(self): @@ -155,43 +172,45 @@ class MjpegStreamingServer(QTcpServer): except ValueError, msg: logger.info("connection %r was not stored?", sock_id) - try: self.stream_clients.remove(sock) except ValueError: logger.info("connection %r was not streaming", sock_id) - + # cleaning up streaming connections if that sock is serving index.html try: stream_client = self.html_map.pop(sock_id) except KeyError: - logger.info("socket %r has no child socket", sock_id) + logger.info("connection %r has no child connections", sock_id) else: try: stream_client.close() + stream_client.deleteLater() except AttributeError, msg: logger.info("no stream client") else: try: self.stream_clients.remove(stream_client) - logger.info("child connection %r removed from streaming", id(stream_client)) + logger.info("child connection %r removed from streaming", + id(stream_client)) except ValueError: pass try: self.sockets.remove(stream_client) - logger.info("child connection %r removed from storage", id(stream_client)) + logger.info("child connection %r removed from storage", + id(stream_client)) except ValueError: pass - def send_image(self): if not self.stream_clients: return img_data = self.widget.render_image() len_data = len(img_data) - array = QByteArray("--2342\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len_data, img_data)) + array = QByteArray("--2342\r\nContent-Type: image/jpeg\r\n" \ + "Content-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len_data, img_data)) for sock in self.stream_clients: sock.write(array) diff --git a/psylib/setup.py b/psylib/setup.py index 40332a1..26f4db6 100644 --- a/psylib/setup.py +++ b/psylib/setup.py @@ -1,9 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from distribute_setup import use_setuptools -use_setuptools() - import sys from setuptools import find_packages, setup @@ -12,7 +9,7 @@ if sys.version_info >= (3,): setup( name='psylib', - version="0.1", + version="0.2", packages=find_packages(exclude=["scripts",]), include_package_data = True, diff --git a/sensors2osc/distribute_setup.py b/sensors2osc/distribute_setup.py new file mode 100644 index 0000000..3553b21 --- /dev/null +++ b/sensors2osc/distribute_setup.py @@ -0,0 +1,556 @@ +#!python +"""Bootstrap distribute installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from distribute_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import shutil +import sys +import time +import fnmatch +import tempfile +import tarfile +import optparse + +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +try: + import subprocess + + def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +except ImportError: + # will be used for python 2.3 + def _python_cmd(*args): + args = (sys.executable,) + args + # quoting arguments if windows + if sys.platform == 'win32': + def quote(arg): + if ' ' in arg: + return '"%s"' % arg + return arg + args = [quote(arg) for arg in args] + return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 + +DEFAULT_VERSION = "0.6.49" +DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" +SETUPTOOLS_FAKED_VERSION = "0.6c11" + +SETUPTOOLS_PKG_INFO = """\ +Metadata-Version: 1.0 +Name: setuptools +Version: %s +Summary: xxxx +Home-page: xxx +Author: xxx +Author-email: xxx +License: xxx +Description: xxx +""" % SETUPTOOLS_FAKED_VERSION + + +def _install(tarball, install_args=()): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # installing + log.warn('Installing Distribute') + if not _python_cmd('setup.py', 'install', *install_args): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + # exitcode will be 2 + return 2 + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + + +def _build_egg(egg, tarball, to_dir): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # building an egg + log.warn('Building a Distribute egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) + sys.path.insert(0, egg) + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15, no_fake=True): + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules + try: + try: + import pkg_resources + + # Setuptools 0.7b and later is a suitable (and preferable) + # substitute for any Distribute version. + try: + pkg_resources.require("setuptools>=0.7b") + return + except (pkg_resources.DistributionNotFound, + pkg_resources.VersionConflict): + pass + + if not hasattr(pkg_resources, '_distribute'): + if not no_fake: + _fake_setuptools() + raise ImportError + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("distribute>=" + version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of distribute (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U distribute'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) + finally: + if not no_fake: + _create_fake_setuptools_pkg_info(to_dir) + + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15): + """Download distribute from a specified location and return its filename + + `version` should be a valid distribute version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + try: + from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen + tgz_name = "distribute-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + log.warn("Downloading %s", url) + src = urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = src.read() + dst = open(saveto, "wb") + dst.write(data) + finally: + if src: + src.close() + if dst: + dst.close() + return os.path.realpath(saveto) + + +def _no_sandbox(function): + def __no_sandbox(*args, **kw): + try: + from setuptools.sandbox import DirectorySandbox + if not hasattr(DirectorySandbox, '_old'): + def violation(*args): + pass + DirectorySandbox._old = DirectorySandbox._violation + DirectorySandbox._violation = violation + patched = True + else: + patched = False + except ImportError: + patched = False + + try: + return function(*args, **kw) + finally: + if patched: + DirectorySandbox._violation = DirectorySandbox._old + del DirectorySandbox._old + + return __no_sandbox + + +def _patch_file(path, content): + """Will backup the file then patch it""" + f = open(path) + existing_content = f.read() + f.close() + if existing_content == content: + # already patched + log.warn('Already patched.') + return False + log.warn('Patching...') + _rename_path(path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + return True + +_patch_file = _no_sandbox(_patch_file) + + +def _same_content(path, content): + f = open(path) + existing_content = f.read() + f.close() + return existing_content == content + + +def _rename_path(path): + new_name = path + '.OLD.%s' % time.time() + log.warn('Renaming %s to %s', path, new_name) + os.rename(path, new_name) + return new_name + + +def _remove_flat_installation(placeholder): + if not os.path.isdir(placeholder): + log.warn('Unkown installation at %s', placeholder) + return False + found = False + for file in os.listdir(placeholder): + if fnmatch.fnmatch(file, 'setuptools*.egg-info'): + found = True + break + if not found: + log.warn('Could not locate setuptools*.egg-info') + return + + log.warn('Moving elements out of the way...') + pkg_info = os.path.join(placeholder, file) + if os.path.isdir(pkg_info): + patched = _patch_egg_dir(pkg_info) + else: + patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) + + if not patched: + log.warn('%s already patched.', pkg_info) + return False + # now let's move the files out of the way + for element in ('setuptools', 'pkg_resources.py', 'site.py'): + element = os.path.join(placeholder, element) + if os.path.exists(element): + _rename_path(element) + else: + log.warn('Could not find the %s element of the ' + 'Setuptools distribution', element) + return True + +_remove_flat_installation = _no_sandbox(_remove_flat_installation) + + +def _after_install(dist): + log.warn('After install bootstrap.') + placeholder = dist.get_command_obj('install').install_purelib + _create_fake_setuptools_pkg_info(placeholder) + + +def _create_fake_setuptools_pkg_info(placeholder): + if not placeholder or not os.path.exists(placeholder): + log.warn('Could not find the install location') + return + pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ + (SETUPTOOLS_FAKED_VERSION, pyver) + pkg_info = os.path.join(placeholder, setuptools_file) + if os.path.exists(pkg_info): + log.warn('%s already exists', pkg_info) + return + + log.warn('Creating %s', pkg_info) + try: + f = open(pkg_info, 'w') + except EnvironmentError: + log.warn("Don't have permissions to write %s, skipping", pkg_info) + return + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + + pth_file = os.path.join(placeholder, 'setuptools.pth') + log.warn('Creating %s', pth_file) + f = open(pth_file, 'w') + try: + f.write(os.path.join(os.curdir, setuptools_file)) + finally: + f.close() + +_create_fake_setuptools_pkg_info = _no_sandbox( + _create_fake_setuptools_pkg_info +) + + +def _patch_egg_dir(path): + # let's check if it's already patched + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + if os.path.exists(pkg_info): + if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): + log.warn('%s already patched.', pkg_info) + return False + _rename_path(path) + os.mkdir(path) + os.mkdir(os.path.join(path, 'EGG-INFO')) + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + return True + +_patch_egg_dir = _no_sandbox(_patch_egg_dir) + + +def _before_install(): + log.warn('Before install bootstrap.') + _fake_setuptools() + + +def _under_prefix(location): + if 'install' not in sys.argv: + return True + args = sys.argv[sys.argv.index('install') + 1:] + for index, arg in enumerate(args): + for option in ('--root', '--prefix'): + if arg.startswith('%s=' % option): + top_dir = arg.split('root=')[-1] + return location.startswith(top_dir) + elif arg == option: + if len(args) > index: + top_dir = args[index + 1] + return location.startswith(top_dir) + if arg == '--user' and USER_SITE is not None: + return location.startswith(USER_SITE) + return True + + +def _fake_setuptools(): + log.warn('Scanning installed packages') + try: + import pkg_resources + except ImportError: + # we're cool + log.warn('Setuptools or Distribute does not seem to be installed.') + return + ws = pkg_resources.working_set + try: + setuptools_dist = ws.find( + pkg_resources.Requirement.parse('setuptools', replacement=False) + ) + except TypeError: + # old distribute API + setuptools_dist = ws.find( + pkg_resources.Requirement.parse('setuptools') + ) + + if setuptools_dist is None: + log.warn('No setuptools distribution found') + return + # detecting if it was already faked + setuptools_location = setuptools_dist.location + log.warn('Setuptools installation detected at %s', setuptools_location) + + # if --root or --preix was provided, and if + # setuptools is not located in them, we don't patch it + if not _under_prefix(setuptools_location): + log.warn('Not patching, --root or --prefix is installing Distribute' + ' in another location') + return + + # let's see if its an egg + if not setuptools_location.endswith('.egg'): + log.warn('Non-egg installation') + res = _remove_flat_installation(setuptools_location) + if not res: + return + else: + log.warn('Egg installation') + pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') + if (os.path.exists(pkg_info) and + _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): + log.warn('Already patched.') + return + log.warn('Patching...') + # let's create a fake egg replacing setuptools one + res = _patch_egg_dir(setuptools_location) + if not res: + return + log.warn('Patching complete.') + _relaunch() + + +def _relaunch(): + log.warn('Relaunching...') + # we have to relaunch the process + # pip marker to avoid a relaunch bug + _cmd1 = ['-c', 'install', '--single-version-externally-managed'] + _cmd2 = ['-c', 'install', '--record'] + if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: + sys.argv[0] = 'setup.py' + args = [sys.executable] + sys.argv + sys.exit(subprocess.call(args)) + + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + +def _build_install_args(options): + """ + Build the arguments to 'python setup.py install' on the distribute package + """ + install_args = [] + if options.user_install: + if sys.version_info < (2, 6): + log.warn("--user requires Python 2.6 or later") + raise SystemExit(1) + install_args.append('--user') + return install_args + +def _parse_args(): + """ + Parse the command line for options + """ + parser = optparse.OptionParser() + parser.add_option( + '--user', dest='user_install', action='store_true', default=False, + help='install in user site package (requires Python 2.6 or later)') + parser.add_option( + '--download-base', dest='download_base', metavar="URL", + default=DEFAULT_URL, + help='alternative URL from where to download the distribute package') + options, args = parser.parse_args() + # positional arguments are ignored + return options + +def main(version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + options = _parse_args() + tarball = download_setuptools(download_base=options.download_base) + return _install(tarball, _build_install_args(options)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/texter/distribute_setup.py b/texter/distribute_setup.py new file mode 100644 index 0000000..3553b21 --- /dev/null +++ b/texter/distribute_setup.py @@ -0,0 +1,556 @@ +#!python +"""Bootstrap distribute installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from distribute_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import shutil +import sys +import time +import fnmatch +import tempfile +import tarfile +import optparse + +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +try: + import subprocess + + def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +except ImportError: + # will be used for python 2.3 + def _python_cmd(*args): + args = (sys.executable,) + args + # quoting arguments if windows + if sys.platform == 'win32': + def quote(arg): + if ' ' in arg: + return '"%s"' % arg + return arg + args = [quote(arg) for arg in args] + return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 + +DEFAULT_VERSION = "0.6.49" +DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" +SETUPTOOLS_FAKED_VERSION = "0.6c11" + +SETUPTOOLS_PKG_INFO = """\ +Metadata-Version: 1.0 +Name: setuptools +Version: %s +Summary: xxxx +Home-page: xxx +Author: xxx +Author-email: xxx +License: xxx +Description: xxx +""" % SETUPTOOLS_FAKED_VERSION + + +def _install(tarball, install_args=()): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # installing + log.warn('Installing Distribute') + if not _python_cmd('setup.py', 'install', *install_args): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + # exitcode will be 2 + return 2 + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + + +def _build_egg(egg, tarball, to_dir): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # building an egg + log.warn('Building a Distribute egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) + sys.path.insert(0, egg) + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15, no_fake=True): + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules + try: + try: + import pkg_resources + + # Setuptools 0.7b and later is a suitable (and preferable) + # substitute for any Distribute version. + try: + pkg_resources.require("setuptools>=0.7b") + return + except (pkg_resources.DistributionNotFound, + pkg_resources.VersionConflict): + pass + + if not hasattr(pkg_resources, '_distribute'): + if not no_fake: + _fake_setuptools() + raise ImportError + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("distribute>=" + version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of distribute (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U distribute'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) + finally: + if not no_fake: + _create_fake_setuptools_pkg_info(to_dir) + + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15): + """Download distribute from a specified location and return its filename + + `version` should be a valid distribute version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + try: + from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen + tgz_name = "distribute-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + log.warn("Downloading %s", url) + src = urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = src.read() + dst = open(saveto, "wb") + dst.write(data) + finally: + if src: + src.close() + if dst: + dst.close() + return os.path.realpath(saveto) + + +def _no_sandbox(function): + def __no_sandbox(*args, **kw): + try: + from setuptools.sandbox import DirectorySandbox + if not hasattr(DirectorySandbox, '_old'): + def violation(*args): + pass + DirectorySandbox._old = DirectorySandbox._violation + DirectorySandbox._violation = violation + patched = True + else: + patched = False + except ImportError: + patched = False + + try: + return function(*args, **kw) + finally: + if patched: + DirectorySandbox._violation = DirectorySandbox._old + del DirectorySandbox._old + + return __no_sandbox + + +def _patch_file(path, content): + """Will backup the file then patch it""" + f = open(path) + existing_content = f.read() + f.close() + if existing_content == content: + # already patched + log.warn('Already patched.') + return False + log.warn('Patching...') + _rename_path(path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + return True + +_patch_file = _no_sandbox(_patch_file) + + +def _same_content(path, content): + f = open(path) + existing_content = f.read() + f.close() + return existing_content == content + + +def _rename_path(path): + new_name = path + '.OLD.%s' % time.time() + log.warn('Renaming %s to %s', path, new_name) + os.rename(path, new_name) + return new_name + + +def _remove_flat_installation(placeholder): + if not os.path.isdir(placeholder): + log.warn('Unkown installation at %s', placeholder) + return False + found = False + for file in os.listdir(placeholder): + if fnmatch.fnmatch(file, 'setuptools*.egg-info'): + found = True + break + if not found: + log.warn('Could not locate setuptools*.egg-info') + return + + log.warn('Moving elements out of the way...') + pkg_info = os.path.join(placeholder, file) + if os.path.isdir(pkg_info): + patched = _patch_egg_dir(pkg_info) + else: + patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) + + if not patched: + log.warn('%s already patched.', pkg_info) + return False + # now let's move the files out of the way + for element in ('setuptools', 'pkg_resources.py', 'site.py'): + element = os.path.join(placeholder, element) + if os.path.exists(element): + _rename_path(element) + else: + log.warn('Could not find the %s element of the ' + 'Setuptools distribution', element) + return True + +_remove_flat_installation = _no_sandbox(_remove_flat_installation) + + +def _after_install(dist): + log.warn('After install bootstrap.') + placeholder = dist.get_command_obj('install').install_purelib + _create_fake_setuptools_pkg_info(placeholder) + + +def _create_fake_setuptools_pkg_info(placeholder): + if not placeholder or not os.path.exists(placeholder): + log.warn('Could not find the install location') + return + pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ + (SETUPTOOLS_FAKED_VERSION, pyver) + pkg_info = os.path.join(placeholder, setuptools_file) + if os.path.exists(pkg_info): + log.warn('%s already exists', pkg_info) + return + + log.warn('Creating %s', pkg_info) + try: + f = open(pkg_info, 'w') + except EnvironmentError: + log.warn("Don't have permissions to write %s, skipping", pkg_info) + return + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + + pth_file = os.path.join(placeholder, 'setuptools.pth') + log.warn('Creating %s', pth_file) + f = open(pth_file, 'w') + try: + f.write(os.path.join(os.curdir, setuptools_file)) + finally: + f.close() + +_create_fake_setuptools_pkg_info = _no_sandbox( + _create_fake_setuptools_pkg_info +) + + +def _patch_egg_dir(path): + # let's check if it's already patched + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + if os.path.exists(pkg_info): + if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): + log.warn('%s already patched.', pkg_info) + return False + _rename_path(path) + os.mkdir(path) + os.mkdir(os.path.join(path, 'EGG-INFO')) + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + return True + +_patch_egg_dir = _no_sandbox(_patch_egg_dir) + + +def _before_install(): + log.warn('Before install bootstrap.') + _fake_setuptools() + + +def _under_prefix(location): + if 'install' not in sys.argv: + return True + args = sys.argv[sys.argv.index('install') + 1:] + for index, arg in enumerate(args): + for option in ('--root', '--prefix'): + if arg.startswith('%s=' % option): + top_dir = arg.split('root=')[-1] + return location.startswith(top_dir) + elif arg == option: + if len(args) > index: + top_dir = args[index + 1] + return location.startswith(top_dir) + if arg == '--user' and USER_SITE is not None: + return location.startswith(USER_SITE) + return True + + +def _fake_setuptools(): + log.warn('Scanning installed packages') + try: + import pkg_resources + except ImportError: + # we're cool + log.warn('Setuptools or Distribute does not seem to be installed.') + return + ws = pkg_resources.working_set + try: + setuptools_dist = ws.find( + pkg_resources.Requirement.parse('setuptools', replacement=False) + ) + except TypeError: + # old distribute API + setuptools_dist = ws.find( + pkg_resources.Requirement.parse('setuptools') + ) + + if setuptools_dist is None: + log.warn('No setuptools distribution found') + return + # detecting if it was already faked + setuptools_location = setuptools_dist.location + log.warn('Setuptools installation detected at %s', setuptools_location) + + # if --root or --preix was provided, and if + # setuptools is not located in them, we don't patch it + if not _under_prefix(setuptools_location): + log.warn('Not patching, --root or --prefix is installing Distribute' + ' in another location') + return + + # let's see if its an egg + if not setuptools_location.endswith('.egg'): + log.warn('Non-egg installation') + res = _remove_flat_installation(setuptools_location) + if not res: + return + else: + log.warn('Egg installation') + pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') + if (os.path.exists(pkg_info) and + _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): + log.warn('Already patched.') + return + log.warn('Patching...') + # let's create a fake egg replacing setuptools one + res = _patch_egg_dir(setuptools_location) + if not res: + return + log.warn('Patching complete.') + _relaunch() + + +def _relaunch(): + log.warn('Relaunching...') + # we have to relaunch the process + # pip marker to avoid a relaunch bug + _cmd1 = ['-c', 'install', '--single-version-externally-managed'] + _cmd2 = ['-c', 'install', '--record'] + if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: + sys.argv[0] = 'setup.py' + args = [sys.executable] + sys.argv + sys.exit(subprocess.call(args)) + + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + +def _build_install_args(options): + """ + Build the arguments to 'python setup.py install' on the distribute package + """ + install_args = [] + if options.user_install: + if sys.version_info < (2, 6): + log.warn("--user requires Python 2.6 or later") + raise SystemExit(1) + install_args.append('--user') + return install_args + +def _parse_args(): + """ + Parse the command line for options + """ + parser = optparse.OptionParser() + parser.add_option( + '--user', dest='user_install', action='store_true', default=False, + help='install in user site package (requires Python 2.6 or later)') + parser.add_option( + '--download-base', dest='download_base', metavar="URL", + default=DEFAULT_URL, + help='alternative URL from where to download the distribute package') + options, args = parser.parse_args() + # positional arguments are ignored + return options + +def main(version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + options = _parse_args() + tarball = download_setuptools(download_base=options.download_base) + return _install(tarball, _build_install_args(options)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/texter/setup.py b/texter/setup.py index d1879a6..48211f6 100644 --- a/texter/setup.py +++ b/texter/setup.py @@ -1,9 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from distribute_setup import use_setuptools -use_setuptools() - import sys from setuptools import find_packages, setup @@ -12,11 +9,13 @@ if sys.version_info >= (3,): setup( name='texter', - version="0.1", + version="0.2", packages=find_packages(exclude=["scripts",]), include_package_data = True, + install_requires = ["psylib"], + package_data = { "texter" : ["*.ui", "*.qrc", "*.png", "*.ico", "*.html"]},