Exemplo n.º 1
0
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()
Exemplo n.º 2
0
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())