class MyForm(QWidget): def __init__(self): QWidget.__init__(self) # setGeometry(x_pos, y_pos, width, height) self.setGeometry(100, 150, 500, 460) self.readSettings() self.setWindowTitle(APPNAME + ' ' + VERSION + " - " + self.chanells_path ) self.icon_path = get_icon_resource(imgdata_png_main) # self.icon_path = os.path.join(os.path.dirname(sys.argv[0]), 'chanchan.ico') self.icon = QIcon(self.icon_path) self.setWindowIcon(self.icon) self.chanells = None self.chanells_all = None self.num_channels = 0 # the player subprocess process self.proc = None self.proc_sopcast = None self.is_sopcast = False self.is_playlist = False self.on_top = False self.cache_size = CACHE_SIZE_DEFAULT if not self.haveSeenFirstTime: copy_default_playlist() self.haveSeenFirstTime = True # saving settings should be done in closeEvent() instead! # settings = QSettings(QSettings.IniFormat, QSettings.UserScope, "xh", "chanchan") # settings.setValue("seen_first_time", QVariant(self.haveSeenFirstTime)) # settings.sync() # use a grid layout for the widgets grid = QGridLayout() # bind the button click to a function reference # new connect style, needs PyQt 4.5+ ###btn_load.clicked.connect(self.load_channels_data) btn_play = QPushButton("&Play") btn_play.setToolTip("Click to play selected stream") btn_play.clicked.connect(self.on_button_play) btn_kill = QPushButton("&Stop") btn_kill.setToolTip("Click to stop current player") btn_kill.clicked.connect(self.kill_proc) self.listbox = QListWidget() # new connect style, needs PyQt 4.5+ self.listbox.clicked.connect(self.on_select) self.listbox.doubleClicked.connect(self.on_double_click) # attach right-click handler self.listbox.setContextMenuPolicy(Qt.ActionsContextMenu) #self.listbox.setContextMenuPolicy(Qt.CustomContextMenu) #http://talk.maemo.org/showthread.php?t=64034 self.actionCopyUrl = QAction("Copy URL", self.listbox) self.connect(self.actionCopyUrl, SIGNAL("triggered()"), self.copy_to_clipboard) self.actionPlay = QAction("Play", self.listbox) self.actionPlay.setShortcut("Ctrl+P") self.actionRestartPlayer = QAction("Restart SopPlayer", self.listbox) self.actionReloadChannels = QAction("Reload List", self.listbox) self.actionEditChannels = QAction("Edit Playlist", self.listbox) self.actionEditChannels.setShortcut("Ctrl+E") self.actionOpenChannelsFile = QAction("Open Playlist File", self.listbox) self.actionEditSource = QAction("Edit Source", self.listbox) self.actionAbout = QAction("About %s" % APPNAME, self.listbox) self.actionQuit = QAction("Quit", self) self.actionQuit.setShortcut("Ctrl+Q") self.search = QLineEdit() self.connect(self.search, SIGNAL("textChanged(QString)"), self.on_search_text_change) # clear button self.clear_button = QToolButton() self.clear_button.setIcon(get_icon_resource(imgdata_png_clear)) self.clear_button.setIconSize(QSize(16, 16)) self.clear_button.setCursor(Qt.ArrowCursor) self.clear_button.setAutoRaise(True) self.clear_button.setEnabled(False) # self.main_layout.addWidget(self.clear_button) self.connect(self.clear_button, SIGNAL("clicked()"), self.clear_search_text) self.listbox.addAction(self.actionPlay) self.listbox.addAction(self.actionRestartPlayer) self.listbox.addAction(self.actionCopyUrl) self.listbox.addAction(self.actionOpenChannelsFile) self.listbox.addAction(self.actionReloadChannels) self.listbox.addAction(self.actionEditChannels) self.listbox.addAction(self.actionEditSource) self.listbox.addAction(self.actionAbout) self.addAction(self.actionQuit) self.connect(self.actionPlay, SIGNAL("triggered()"), self.on_double_click) self.connect(self.actionRestartPlayer, SIGNAL("triggered()"), self.restart_sopplayer) self.connect(self.actionReloadChannels, SIGNAL("triggered()"), lambda: self.load_channels_data(self.chanells_path)) self.connect(self.actionEditChannels, SIGNAL("triggered()"), lambda: self.edit_file(str(self.chanells_path))) self.connect(self.actionOpenChannelsFile, SIGNAL("triggered()"), lambda: self.load_channels_data()) self.connect(self.actionEditSource, SIGNAL("triggered()"), lambda: self.edit_file(path=sys.argv[0], editor=EDITOR)) self.connect(self.actionQuit, SIGNAL("triggered()"), self.close) self.connect(self.actionAbout, SIGNAL("triggered()"), lambda: QMessageBox.about(self, 'About %s' % APPNAME, ''' <h4>%s version %s</h4> <p> Created by <i>%s</i></p> <p><a href="mailto:%s">%s</a></p> <p><a href="%s">chanchantv.googlecode.com</a></p> ''' % (APPNAME, VERSION, AUTHOR, EMAIL.decode('base64'), EMAIL, WEB)) # warning(self, APPNAME, 'No playlist selected') ) # self.listbox.connect(self.listbox, SIGNAL("customContextMenuRequested(QPoint)"), # self.on_right_click) # self.txtChanInfo = QLineEdit() # self.txtChanInfo.setReadOnly(True) # self.logWindow = QTextEdit() # self.logWindow.setSizePolicyx(QSizePolicy.) self.status = QLabel() self.status.setText('channels') # ADD BEVELED BORDER::self.status.setFrameStyle(QFrame.Panel | QFrame.Sunken) self.groupBox = QGroupBox("Engine") sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) self.groupBox.setSizePolicy(sizePolicy) # self.groupBox.setAutoFillBackground(True) self.rbMplayer = QRadioButton('&Mplayer', self.groupBox) self.rbMplayer.setChecked(True) self.rbMplayer.setToolTip("Play with Mplayer") #self.rbGstreamer = QRadioButton('gst&123', self.groupBox) self.rbVlc = QRadioButton('&Vlc', self.groupBox) self.rbVlc.setToolTip("Play with VLC") self.rbTotem = QRadioButton('&Totem', self.groupBox) self.rbTotem.setToolTip("Play with Totem") self.rbBrowser = QRadioButton('&Browser', self.groupBox) self.rbBrowser.setToolTip("Open URL in web browser") self.hBoxTop = QHBoxLayout() self.hBoxTop.addWidget(self.rbMplayer) #self.hBoxTop.addWidget(self.rbGstreamer) self.hBoxTop.addWidget(self.rbVlc) self.hBoxTop.addWidget(self.rbTotem) self.hBoxTop.addWidget(self.rbBrowser) self.groupBox.setLayout(self.hBoxTop) self.cbPlaylistFlag = QCheckBox('Playlist') self.cbPlaylistFlag.setToolTip('Resource is a M3U, ASX or PLS playlist') self.cbFullScreen = QCheckBox('Full Screen') self.cbFullScreen.setToolTip('Start video in full screen') self.cbFullScreen.setChecked(self.is_full_screen) self.cbInhibitScreensaver = QCheckBox('Inhibit Screensaver') self.cbInhibitScreensaver.setToolTip('Disable screensaver while playing stream') self.cbInhibitScreensaver.setChecked(self.is_inhibit_screen) # addWidget(widget, row, column, rowSpan, columnSpan) grid.addWidget(self.groupBox, 0, 0, 1, 3) grid.addWidget(btn_play, 0, 4, 1, 1) grid.addWidget(btn_kill, 0, 5, 1, 1) grid.addWidget(self.search, 1, 0, 1, 4) grid.addWidget(self.clear_button, 1, 3, 1, 1) grid.addWidget(self.status, 1, 5, 1, 1) # listbox spans over 5 rows and 2 columns grid.addWidget(self.listbox, 2, 0, 5, 6) ## BAD grid.addWidget(self.hBoxFlags, 6, 0, 1, 1) grid.addWidget(self.cbPlaylistFlag, 7, 0, 1, 1) grid.addWidget(self.cbFullScreen, 7, 1, 1, 1) grid.addWidget(self.cbInhibitScreensaver, 7, 2, 1, 1) # grid.addWidget(self.txtChanInfo, 7, 0, 1, 6) # grid.addWidget(self.logWindow, 8, 0, 1, 6) self.setLayout(grid) self.search.setFocus() self.load_channels_data(self.chanells_path) def clear_search_text(self): print '------clear-search-text---------' self.search.clear() def on_search_text_change(self): if not self.chanells_all: # only need to do this once self.chanells_all = list(self.chanells) text = str(self.search.text()).strip() print 'DBG', len(text), len(self.chanells_all) if len(text) > 1: self.clear_button.setEnabled(True) filtered_list = self.get_matching_items(text.lower(), self.chanells_all) if len(filtered_list): self.chanells = filtered_list else: self.chanells = [] else: self.chanells = list(self.chanells_all) self.clear_button.setEnabled(False) self.load_channels_data(None, False) def get_matching_items(self, needle, haystack): 'search for a substring in channel list' matches = [] found_in_meta = False last_meta_item = None for ch in haystack: is_meta = ch.startswith('#') if is_meta and not needle in ch.lower(): last_meta_item = ch if needle in ch.lower(): if is_meta: found_in_meta = True elif not found_in_meta and last_meta_item not in matches: matches.append(last_meta_item) matches.append(ch) elif found_in_meta: if not is_meta: matches.append(ch) else: found_in_meta = False return matches def closeEvent(self, event): self.writeSettings() print 'closeEvent: Saving settings and exiting...' return quit_msg = "Are you sure you want to exit the program?" reply = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self.writeSettings() event.accept() QApplication.instance().quit() else: event.ignore() def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.search.setFocus() self.search.selectAll() def copy_to_clipboard(self): clipboard = QApplication.clipboard() clipboard.setText(self.listbox.currentItem().text()) def load_channels_data(self, new_path=None, read_from_file=True): MediaItem.num_items = 0 if read_from_file: if not new_path: new_path = str(self.get_new_filename()) if not new_path: # QMessageBox.warning(self, APPNAME, 'No playlist selected') return try: fh = codecs.open(new_path, 'r', 'utf8') self.chanells = [ch.strip() for ch in fh.readlines()] except Exception as e: show_gui_error(e, 'File not found', 'Error opening playlist "%s" \n\n%s' % (new_path, str(e))) return self.chanells_path = new_path self.chanells = [ch.strip() for ch in self.chanells] self.chanells_all = None self.listbox.clear() current_params = None for chan in self.chanells: if not len(chan) or chan.strip() == '#EXTM3U': continue item = MediaItem(chan, self.icon) if item.is_meta: '''if a metadata line, then store and apply to all the following non-metadata items ''' current_params = item.params elif current_params: item.params = current_params item.setStatusTip(chan) self.listbox.addItem(item) self.setWindowTitle(APPNAME + ' ' + VERSION + ' - ' + self.chanells_path) self.status.setText(str(MediaItem.num_items) + ' channels') def edit_file(self, path, editor=EDITOR): if ' ' in editor: editor = editor.split(' ') subprocess.Popen([editor, path]) def get_new_filename(self): return QFileDialog.getOpenFileName(self, 'Load Playlist file', '', "Playlist files (*.m3u);;All Files (*.*);") def on_button_play(self): # self.on_select() self.play_media() def on_select(self): """an item in the listbox has been clicked/selected""" current_item = self.listbox.currentItem() if not current_item: return current_channel = current_item and str(current_item.text()) or '<no channel>' self.is_playlist = current_channel[-4:].lower() in ['.m3u', '.asx', '.pls'] # if current_channel.startswith('sop:'): #self.rbMplayer.setChecked(True) # self.cache_size = '1024' if current_item.params: 'set params for current channel according to metadata line' myparams = current_item.params.keys() if 'player' in myparams: player = current_item.params['player'].lower() if player == 'totem': self.rbTotem.setChecked(True) elif player == MPLAYER: self.rbMplayer.setChecked(True) #elif player == 'gst123': # self.rbGstreamer.setChecked(True) elif player == VLC: self.rbVlc.setChecked(True) elif player in ('browser', 'web'): self.rbBrowser.setChecked(True) if 'playlist' in myparams or 'pl' in myparams: self.is_playlist = current_item.params['playlist'].lower() in GOOD_VALUES if 'fullscreen' in myparams or 'fs' in myparams: self.is_full_screen = current_item.params['fullscreen'].lower() in GOOD_VALUES else: self.is_full_screen = IS_FULLSCREEN_DEFAULT if 'ontop' in myparams or 'top' in myparams: self.on_top = current_item.params['top'].lower() in GOOD_VALUES else: self.on_top = IS_ON_TOP_DEFAULT if 'cache' in myparams: self.cache_size = current_item.params['cache'] else: self.cache_size = CACHE_SIZE_DEFAULT if 'exec' in myparams or 'shell' in myparams: # set shell options: console or no console self.exec_shell_command = current_item.params['exec'] # # if 'exec' in myparams: # self.executable_name = current_item.params['exec'] self.cbPlaylistFlag.setChecked(self.is_playlist) # only setting True state if self.is_full_screen: self.cbFullScreen.setChecked(True) def on_double_click(self): self.play_media() """an item in the listbox has been double-clicked""" def restart_sopplayer(self): # if self.rbVlc.isChecked(): # if vlc_remote_command('testing if vlc remote is running...'): # vlc_remote_command('add %s' % SOPCAST_SERVER_URL) # vlc_remote_command('volume 200') # else: self.play_media(start_sopcast_server=False) def play_media(self, start_sopcast_server=True): current_item = self.listbox.currentItem() if not current_item: return current_channel = str(current_item.text()) if self.proc and self.proc.pid: self.kill_proc() args = [] if self.cbInhibitScreensaver.isChecked(): suspend_screensaver() ################ RUN SHELL COMMAND ############# if 'exec' in current_item.params: show_console = current_item.params['exec'].lower() == 'console' if show_console: args += ['xterm', '-geometry', '45x8-20+400', '-e'] args += [current_channel.strip()] self.proc = subprocess.Popen(args, stdout=subprocess.PIPE) print 'DBG:', self.proc else: args.insert(0, current_item.params['exec']) args += [current_channel.strip()] self.proc = subprocess.Popen(args, shell=True) return # don't use xterm for vlc, totem if (self.rbMplayer.isChecked()): if not is_win32 and not is_osx: args += ['xterm', '-geometry', '45x8-20+150', '-e'] self.is_sopcast = current_channel.lower().startswith('sop://') if self.is_sopcast: if start_sopcast_server: # args_sopcast = ['xterm', '-geometry', '45x8-20+400', '-e', sopcast_binary, current_channel, SOPCAST_LISTEN_PORT, SOPCAST_SERVER_PORT] try: print 'Waiting for sopcast server starup at %s ...' % current_channel self.proc_sopcast = run_command_in_new_terminal( sopcast_binary, current_channel, SOPCAST_LISTEN_PORT, SOPCAST_SERVER_PORT) # except Exception as e: show_gui_error(e, """ERROR! Sopcast executable not found or other error: To install sopcast support on Linux, run: %s""" % (SOPCAST_INSTALL_HOWTO)) return current_channel = SOPCAST_SERVER_URL time.sleep(SOPCAST_SERVER_WAIT_SECS) if self.rbMplayer.isChecked(): if is_win32: args = ['cmd', '/c', MPLAYER_PATH_WIN32, '-cache-min', CACHE_SIZE_MIN, '-cache', self.cache_size] else: args += [MPLAYER, '-cache-min', CACHE_SIZE_MIN, '-cache', self.cache_size] self.on_top and args.append('-ontop') self.cbFullScreen.isChecked() and args.append('-fs') self.cbPlaylistFlag.isChecked() and args.append('-playlist') #elif self.rbGstreamer.isChecked(): # args.append('gst123') # if '.m3u' in current_channel: # current_channel = getFirstUrl(current_channel) elif self.rbVlc.isChecked(): if is_win32: if os.path.exists(VLC_PATH_WIN32): args = [VLC_PATH_WIN32] elif os.path.exists(VLC_PATH_WIN32_CUSTOM): args = [VLC_PATH_WIN32_CUSTOM] elif is_osx: args = [VLC_PATH_OSX]#, '--one-instance'] else: args = ['vlc']#, '--one-instance'] # if vlc_remote_command('testing if vlc remote is running...'): # # print 'VLC Remote Control not running, starting new VLC instance...' # vlc_remote_command('add %s' % current_channel) # vlc_remote_command('volume 150') # return # else: # print 'VLC Remote Control not running, starting new VLC instance...' self.cbFullScreen.isChecked() and args.append('--fullscreen') args.append('--video-on-top') elif self.rbTotem.isChecked(): args += ['totem', '--replace'] # FIXME!!! totem segfaults when started with the --fullscreen switch # self.cbFullScreen.isChecked() and args.append('--fullscreen') elif self.rbBrowser.isChecked(): open_webbrowser(current_channel) return args.append(current_channel) print args try: if is_win32: #self.proc = subprocess.Popen(args, creationflags=subprocess.STARTF_USESHOWWINDOW, cwd=os.path.dirname(sys.argv[0])) ## TODO: get right options on win32 #http://stackoverflow.com/questions/7006238/how-do-i-hide-the-console-when-i-use-os-system-or-subprocess-call #startupinfo = subprocess.STARTUPINFO() #startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW #subprocess.call('taskkill /F /IM exename.exe', startupinfo=startupinfo) self.proc = subprocess.Popen(args, shell=True, cwd=os.path.dirname(sys.argv[0])) else: self.proc = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE) print 'DBG:', type(self.proc), self.proc # console_data = self.proc.stdout.read() # self.logWindow.setText(console_data) except Exception as e: show_gui_error(e, "ERROR! Selected player not available:\n") def kill_proc(self): if self.cbInhibitScreensaver.isChecked(): resume_screensaver() if self.proc and not self.rbVlc.isChecked(): try: self.proc.kill() except: pass if self.is_sopcast and self.proc_sopcast: try: self.proc_sopcast.kill() os.system('killall sopcast') except: pass def readSettings(self): # store settings object self.settings = QSettings(QSettings.IniFormat, QSettings.UserScope, "xh", "chanchan") pos = self.settings.value("pos", QVariant(QPoint(200, 200))).toPoint() size = self.settings.value("size", QVariant(QSize(400, 400))).toSize() self.resize(size) self.move(pos) self.chanells_path = self.settings.contains('channels_file') and str(self.settings.value("channels_file").toString()) or get_default_channels_path() self.is_inhibit_screen = self.settings.contains('inhibit_screen') and self.settings.value("inhibit_screen").toBool() self.is_full_screen = self.settings.contains('fullscreen') and self.settings.value("fullscreen").toBool() self.haveSeenFirstTime = self.settings.contains('seen_first_time') and self.settings.value("seen_first_time").toBool() def writeSettings(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, "xh", "chanchan") settings.setValue("pos", QVariant(self.pos())) settings.setValue("size", QVariant(self.size())) settings.setValue("channels_file", QVariant(self.chanells_path)) settings.setValue("inhibit_screen", QVariant(self.cbInhibitScreensaver.isChecked())) settings.setValue("fullscreen", QVariant(self.cbFullScreen.isChecked())) settings.setValue("seen_first_time", QVariant(self.haveSeenFirstTime)) settings.sync()
class SeriesPreview(QDialog): def __init__(self, parent=None): super(SeriesPreview, self).__init__(parent) self.data = None self.workingSet = None self.list = QListWidget() self.cancel = QPushButton('Close') self.apply = QPushButton('Update') self.layout = QGridLayout() self.layout.addWidget(self.list, 0, 0, 1, 2) self.layout.addWidget(self.apply, 1, 0) self.layout.addWidget(self.cancel, 1, 1) self.setLayout(self.layout) self.initComponents() self.initActions() def initComponents(self): self.setWindowFlags(Qt.Tool) self.setWindowTitle("Time series") self.setStyleSheet('''QPushButton { color: #333; border: 1px solid #555; border-radius: 11px; padding: 2px; background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, radius: 1.35, stop: 0 #fff, stop: 1 #888); min-width: 80px; } QPushButton:hover { color: #fff; background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, radius: 1.35, stop: 0 #fff, stop: 1 #bbb);} QPushButton:pressed { background: qradialgradient(cx: 0.4, cy: -0.1, fx: 0.4, fy: -0.1, radius: 1.35, stop: 0 #fff, stop: 1 #ddd);} QPushButton:checked { background: qradialgradient(cx: 0.4, cy: -0.1, fx: 0.4, fy: -0.1, radius: 1.35, stop: 0 #fff, stop: 1 #ddd);} QListView::focus { border: 2px solid black; border-radius: 6px; } QScrollBar:vertical { width: 20px; border: 1px solid grey; border-radius: 6px; background-color: transparent; margin: 28px 0 28px 0; } QScrollBar::add-line:vertical { background: transparent; height: 32px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line:vertical { background: transparent; height: 32px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar::up-arrow:vertical { width: 20px; height: 32px; background: transparent; image: url(../res/icons/arrow_up.png); } QScrollBar::up-arrow:hover { bottom: 2px; } QScrollBar::down-arrow:vertical { width: 20px; height: 32px; background: transparent; image: url(../res/icons/arrow_down.png); } QScrollBar::down-arrow:hover { top: 2px; } QScrollBar::handle:vertical { border-radius: 6px; background: url(../res/icons/handle.png) 0% center no-repeat; background-color: white; min-height: 32px; } QScrollBar::handle:hover { background: url(../res/icons/handle_hover.png) 0% center no-repeat; background-color: white; border: 1px solid gray; }''') self.list.setAlternatingRowColors(True) self.list.setStyleSheet('''QListView::item:selected:active { background: qlineargradient(x1: 1, y1: 0, x2: 0, y2: 3, stop: 0 #cbdaf1, stop: 1 #bfcde4); } QListView::item { border: 1px solid #d9d9d9; border-top-color: transparent; border-bottom-color: transparent; } QListView::item:hover { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #e7effd, stop: 1 #cbdaf1); border: 1px solid #bfcde4; }''') self.list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.list.setContextMenuPolicy(Qt.ActionsContextMenu) def initActions(self): self.apply.clicked.connect(self.applyChanges) self.cancel.clicked.connect(self.close) self.list.itemDoubleClicked.connect(self.removeFromList) self.list.addAction(QAction('&Remove selected', self, triggered=self.removeItems)) #--- actions ---# def updateData(self, data): self.data = data self.workingSet = data[0][:] self.updateList() def updateList(self): self.list.clear() for item in self.workingSet: item = QListWidgetItem(str(item)) item.setTextAlignment(Qt.AlignCenter) self.list.addItem(item) def applyChanges(self): self.data = (self.workingSet, self.data[1]) def removeFromList(self, item): self.workingSet.remove(float(item.text())) self.list.takeItem(self.list.indexFromItem(item).row()) def removeItems(self): for item in self.list.selectedItems(): self.workingSet.remove(float(item.text())) self.list.takeItem(self.list.indexFromItem(item).row())