Beispiel #1
0
class RemoteView(Tab):
    """docstring for RemoteView"""
    def __init__(self, parent=None):
        super(RemoteView, self).__init__(parent)

        self.client = None
        self.current_state = STATE_DISCONNECTED

        self.grid_info = QGridLayout()
        self.hbox_search = QHBoxLayout()
        self.hbox_admin = QHBoxLayout()
        self.vbox = QVBoxLayout(self)

        self.dashboard = Dashboard(self)

        self.edit_hostname = QLineEdit(self)
        self.edit_username = QLineEdit(self)
        self.edit_apikey = QLineEdit(self)
        self.edit_dir = QLineEdit(self)

        self.tbl_remote = RemoteTable(self)
        self.tbl_remote.showColumnHeader(True)
        self.tbl_remote.showRowHeader(False)

        self.edit_search = LineEdit_Search(self, self.tbl_remote,
                                           "Search Remote")

        self.btn_connect = QPushButton("Connect", self)

        lbl = QLabel("Hostname:")
        lbl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.grid_info.addWidget(lbl, 0, 0)
        self.grid_info.addWidget(self.edit_hostname, 0, 1)

        lbl = QLabel("User Name:")
        lbl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.grid_info.addWidget(lbl, 0, 2)
        self.grid_info.addWidget(self.edit_username, 0, 3)

        lbl = QLabel("Local Directory:")
        lbl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.grid_info.addWidget(lbl, 1, 0)
        self.grid_info.addWidget(self.edit_dir, 1, 1)

        lbl = QLabel("API Key:")
        lbl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.grid_info.addWidget(lbl, 1, 2)
        self.grid_info.addWidget(self.edit_apikey, 1, 3)

        self.lbl_error = QLabel("")
        self.lbl_search = QLabel("")

        self.cb_remote = QComboBox(self)
        self.cb_remote.addItem("Show All", SONG_ALL)
        self.cb_remote.addItem("Show Remote", SONG_REMOTE)  # index 1
        self.cb_remote.addItem("Show Local", SONG_LOCAL)  # index 2
        self.cb_remote.addItem("Show Sync", SONG_SYNCED)  # index 3
        self.cb_remote.currentIndexChanged.connect(self.onRemoteIndexChanged)

        self.btn_push = QPushButton("Push", self)
        self.btn_pull = QPushButton("Pull", self)
        self.btn_upload = QPushButton("Upload", self)
        self.btn_download = QPushButton("Download", self)
        self.chk_mode = QCheckBox("Master", self)
        self.chk_path = QCheckBox("Update Path", self)

        self.lbl_library = QLabel("Library:")
        self.lbl_library.setAlignment(Qt.AlignRight | Qt.AlignVCenter)

        self.lbl_history = QLabel("History:")
        self.lbl_history.setAlignment(Qt.AlignRight | Qt.AlignVCenter)

        self.hbox_search.addWidget(self.btn_connect)
        self.hbox_search.addWidget(self.edit_search)
        self.hbox_search.addWidget(self.cb_remote)
        self.hbox_search.addWidget(self.lbl_search)

        self.hbox_admin.addWidget(self.chk_mode)
        self.hbox_admin.addWidget(self.chk_path)
        self.hbox_admin.addWidget(self.lbl_history)
        #self.hbox_admin.addWidget(self.btn_hardsync)
        #self.hbox_admin.addWidget(self.btn_hardpush)
        self.hbox_admin.addWidget(self.btn_push)
        self.hbox_admin.addWidget(self.btn_pull)
        #self.hbox_admin.addWidget(self.btn_delete)

        self.hbox_admin.addWidget(self.lbl_library)
        #self.hbox_admin.addWidget(self.btn_hardsync)
        #self.hbox_admin.addWidget(self.btn_hardpush)
        self.hbox_admin.addWidget(self.btn_upload)
        self.hbox_admin.addWidget(self.btn_download)
        #self.hbox_admin.addWidget(self.btn_delete)

        self.vbox.addLayout(self.grid_info)
        self.vbox.addLayout(self.hbox_admin)
        self.vbox.addLayout(self.hbox_search)
        self.vbox.addWidget(self.lbl_error)
        self.vbox.addWidget(self.tbl_remote.container)
        self.vbox.addWidget(self.dashboard)

        self.edit_hostname.setText(Settings.instance()['remote_hostname'])
        self.edit_username.setText(Settings.instance()['remote_username'])
        # "a10ddf873662f4aabd67f62c799ecfbb"
        self.edit_apikey.setText(Settings.instance()['remote_apikey'])
        self.edit_dir.setText(Settings.instance()['remote_basedir'])

        self.edit_search.textChanged.connect(self.onSearchTextChanged)
        self.btn_connect.clicked.connect(self.onConnectClicked)

        self.btn_pull.clicked.connect(self.onHistoryPullClicked)
        self.btn_push.clicked.connect(self.onHistoryPushClicked)
        self.btn_upload.clicked.connect(self.onLibraryUploadClicked)
        self.btn_download.clicked.connect(self.onLibraryDownloadClicked)
        self.chk_mode.stateChanged.connect(self.onModeStateChanged)
        #self.btn_delete.clicked.connect(self.onHistoryDeleteClicked)
        #self.btn_hardsync.clicked.connect(self.onHistoryHardSyncClicked)
        #self.btn_hardpush.clicked.connect(self.onHistoryHardPushClicked)

        self.tbl_remote.update_data.connect(self.refresh)  # on sort...

        self.grammar = RemoteSongSearchGrammar()
        self.song_library = []
        """
        # generate simple library for testing purposes.
        songs = Library.instance().search("",limit=100)
        for song in songs:
            song['remote'] = SONG_REMOTE
        self.song_library = songs
        """
        self.setSongs(self.song_library)
        self.chk_mode.setChecked(Qt.Checked)
        self.hbox_admin.setEnabled(False)
        self.edit_search.setEnabled(False)
        self.cb_remote.setEnabled(False)

    def onSearchTextChanged(self):

        text = self.edit_search.text()
        self.run_search(text)

    def refresh(self):
        self.run_search(self.edit_search.text())

    def run_search(self, text):

        try:
            rule = self.grammar.ruleFromString(text)
            limit = self.grammar.getMetaValue("limit", None)
            offset = self.grammar.getMetaValue("offset", 0)

            # TODO: instead of comparing index, store the
            # enum value with the combo box item
            remote = int(self.cb_remote.currentData())
            if remote == SONG_SYNCED:
                rule = AndSearchRule.join(
                    rule, ExactSearchRule(Song.remote, SONG_SYNCED, type_=int))
            elif remote == SONG_LOCAL:
                rule = AndSearchRule.join(
                    rule, ExactSearchRule(Song.remote, SONG_LOCAL, type_=int))
            elif remote == SONG_REMOTE:
                rule = AndSearchRule.join(
                    rule, ExactSearchRule(Song.remote, SONG_REMOTE, type_=int))

            songs = naive_search(self.song_library,
                                 rule,
                                 orderby=self.tbl_remote.sort_orderby,
                                 reverse=self.tbl_remote.sort_reverse,
                                 limit=limit,
                                 offset=offset)

            self.setSongs(songs)

        except ParseError as e:
            self.edit_search.setStyleSheet("background: #CC0000;")
            self.lbl_error.setText("%s" % e)
            self.lbl_error.show()

    def setSongs(self, songs):

        self.tbl_remote.setData(songs)
        self.edit_search.setStyleSheet("")
        self.lbl_error.hide()
        self.lbl_search.setText("%d/%d" % (len(songs), len(self.song_library)))

    def getNewClient(self):

        hostname = self.edit_hostname.text().strip()
        apikey = self.edit_apikey.text().strip()
        username = self.edit_username.text().strip()

        if self.client is None:
            self.client = ApiClientWrapper(ApiClient(hostname))
            self.client.setApiKey(apikey)
            self.client.setApiUser(username)

        return self.client

    def onConnectClicked(self):

        if self.current_state == STATE_DISCONNECTED:
            # get a new client, try to connect
            self.client = None
            client = self.getNewClient()

            basedir = self.edit_dir.text().strip()

            self.btn_connect.setEnabled(False)

            job = ConnectJob(client, basedir)
            job.newLibrary.connect(self.onNewLibrary)
            job.newApiKey.connect(self.onNewApiKey)
            job.complete.connect(self.onConnectComplete)
            self.dashboard.startJob(job)

            self.current_state = STATE_CONNECTING

        elif self.current_state == STATE_CONNECTED:
            self.onConnectComplete(False)

    def onConnectComplete(self, success):

        self.btn_connect.setEnabled(True)

        if success:
            self.btn_connect.setText("Disconnect")
            self.hbox_admin.setEnabled(True)
            self.edit_search.setEnabled(True)
            self.cb_remote.setEnabled(True)
            self.current_state = STATE_CONNECTED
        else:
            self.client = None
            self.btn_connect.setText("Connect")
            self.hbox_admin.setEnabled(False)
            self.edit_search.setEnabled(False)
            self.cb_remote.setEnabled(False)
            self.current_state = STATE_DISCONNECTED

    def onRemoteIndexChanged(self, idx):
        self.refresh()

    def onNewApiKey(self, username, apikey):

        self.edit_username.setText(username)
        self.edit_apikey.setText(apikey)

    def onNewLibrary(self, songs):
        # TODO: convert the connect button to a disconnect button
        self.song_library = songs
        self.refresh()  # run the current query, update table

    def action_downloadSelection(self, items):
        client = self.getNewClient()
        job = DownloadJob(client, items, self.edit_dir.text())
        self.dashboard.startJob(job)

    def action_uploadSelection(self, items):
        client = self.getNewClient()
        upload_filepath = self.chk_path.isChecked()
        job = UploadJob(client, items, self.edit_dir.text(), upload_filepath)
        job.finished.connect(self.refresh)
        self.dashboard.startJob(job)

    def onHistoryPullClicked(self):
        client = self.getNewClient()
        job = HistoryPullJob(client)
        self.dashboard.startJob(job)

    def onHistoryPushClicked(self):
        client = self.getNewClient()
        job = HistoryPushJob(client)
        self.dashboard.startJob(job)

    def onLibraryUploadClicked(self):
        client = self.getNewClient()
        upload_filepath = self.chk_path.isChecked()
        job = UploadJob(client, self.song_library, self.edit_dir.text(),
                        upload_filepath)
        job.finished.connect(self.refresh)
        self.dashboard.startJob(job)

    def onLibraryDownloadClicked(self):
        client = self.getNewClient()
        job = DownloadMetadataJob(client, self.song_library)
        job.finished.connect(self.refresh)
        self.dashboard.startJob(job)

    def onModeStateChanged(self, state):

        checked = state == Qt.Checked
        self.btn_push.setEnabled(not checked)
        self.btn_pull.setEnabled(checked)
        self.btn_upload.setEnabled(checked)
        self.btn_download.setEnabled(not checked)
class ExplorerView(Tab):
    """docstring for ExplorerView"""

    play_file = pyqtSignal(str)
    ingest_finished = pyqtSignal()

    primaryDirectoryChanged = pyqtSignal(str)
    secondaryDirectoryChanged = pyqtSignal(str)

    submitJob = pyqtSignal(Job)


    def __init__(self, parent=None):
        super(ExplorerView, self).__init__(parent)

        self.controller = YueExplorerController( )

        self.source = DirectorySource()

        self.dashboard = Dashboard(self)

        self.ex_main = YueExplorerModel( None, self.controller, self )
        self.ex_secondary = YueExplorerModel( None, self.controller, self )
        self.ex_secondary.btn_split.setIcon(QIcon(":/img/app_join.png"))

        self.ex_main.do_ingest.connect(self.onIngestPaths)
        self.ex_secondary.do_ingest.connect(self.onIngestPaths)

        self.ex_main.toggleSecondaryView.connect(self.onToggleSecondaryView)
        self.ex_secondary.toggleSecondaryView.connect(self.onToggleSecondaryView)

        self.ex_main.directoryChanged.connect(self.primaryDirectoryChanged)
        self.ex_secondary.directoryChanged.connect(self.secondaryDirectoryChanged)

        self.ex_main.showSplitButton(True)
        self.ex_secondary.hide()

        self.hbox = QHBoxLayout()
        self.hbox.setContentsMargins(0,0,0,0)
        self.hbox.addWidget(self.ex_main)
        self.hbox.addWidget(self.ex_secondary)

        self.vbox = QVBoxLayout(self)
        self.vbox.setContentsMargins(0,0,0,0)

        self.vbox.addLayout(self.hbox)
        self.vbox.addWidget(self.dashboard)

    def onEnter(self):

        # delay creating the views until the tab is entered for the first
        # time, this slightly improves startup performance
        if (self.ex_main.view is None):
            view1 = LazySourceListView(self.source,self.source.root())
            view2 = LazySourceListView(self.source,self.source.root())

            # this is a hack, source views are currently in flux
            view1.chdir(view1.pwd())
            view2.chdir(view2.pwd())

            view1.loadDirectory.connect(lambda : self.onLazyLoadDirectory(self.ex_main))
            view2.loadDirectory.connect(lambda : self.onLazyLoadDirectory(self.ex_secondary))

            self.ex_main.setView(view1)
            self.ex_secondary.setView(view2)

            self.ex_main.play_file.connect(self.onPlayFile)
            self.ex_secondary.play_file.connect(self.onPlayFile)

            self.ex_main.submitJob.connect(self.dashboard.startJob)
            self.ex_secondary.submitJob.connect(self.dashboard.startJob)
            self.controller.submitJob.connect(self.dashboard.startJob)

            self.ex_main.chdir("~")
            self.ex_secondary.chdir("~")

    def chdir(self, path):

        # chdir can be called from another Tab, prior to onEnter,
        # if that happens run the onEnter first time setup.
        self.onEnter()

        self.ex_main.chdir(path)

    def onLazyLoadDirectory(self,model):

        job = LoadDirectoryJob(model)
        self.dashboard.startJob(job)

    def refresh(self):
        self.ex_main.refresh()
        if self.ex_secondary.isVisible():
            self.ex_secondary.refresh()

    def onToggleSecondaryView(self):
        if self.ex_secondary.isHidden():
            self.ex_secondary.show()
            self.ex_main.showSplitButton(False)
            self.ex_secondary.showSplitButton(True)
        else:
            self.ex_secondary.hide()
            self.ex_main.showSplitButton(True)
            self.ex_secondary.showSplitButton(False)

    def onIngestPaths(self,paths):

        self.controller.dialog = IngestProgressDialog(paths,self)
        self.controller.dialog.finished.connect(self.onDialogFinished)

        self.controller.dialog.show()
        self.controller.dialog.start()

    def onDialogFinished(self):
        if isinstance(self.controller.dialog,IngestProgressDialog):
            self.ingest_finished.emit()
        self.controller.dialog = None
        self.ex_main.refresh()
        self.ex_secondary.refresh()

    def onPlayFile(self,path):
        self.play_file.emit(path)
Beispiel #3
0
class MainWindow(QMainWindow):
    """docstring for MainWindow"""
    def __init__(self, version_info, defaultpath, defaultpath_r=""):
        """
        defaultpath: if empty default to the users home
        """
        super(MainWindow, self).__init__()

        self.sources = set()

        self.version, self.versiondate, self.builddate = version_info

        self.initMenuBar()
        self.initStatusBar()

        self.splitter = QSplitter(self)

        self.pain_main = Pane(self)

        self.dashboard = Dashboard(self)

        # controller maintains state between tabs
        self.source = DirectorySource()
        self.controller = ExplorController(self)
        self.controller.forceReload.connect(self.refresh)
        self.controller.submitJob.connect(self.dashboard.startJob)
        self.controller.viewImage.connect(self.onViewImage)

        self.btn_newTab = QToolButton(self)
        self.btn_newTab.clicked.connect(self.newTab)
        self.btn_newTab.setIcon(QIcon(":/img/app_plus.png"))

        self.quickview = QuickAccessTable(self)
        self.quickview.changeDirectory.connect(self.onChangeDirectory)
        self.quickview.changeDirectoryRight.connect(
            self.onChangeDirectoryRight)

        self.wfctrl = WatchFileController(self.source)
        self.wfctrl.watchersChanged.connect(self.onWatchersChanged)

        self.wfview = WatchFileTable(self.wfctrl, self)
        self.wfview.changeDirectory.connect(self.onChangeDirectory)

        self.tabview = TabWidget(self)
        self.tabview.tabBar().setMovable(True)
        self.tabview.setCornerWidget(self.btn_newTab)
        self.tabview.tabCloseRequested.connect(self.onTabCloseRequest)
        self.tabview.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        self.tabview.tabBar().setUsesScrollButtons(True)
        self.tabview.tabBar().setElideMode(Qt.ElideNone)

        self.pain_main.addWidget(self.tabview)
        self.pain_main.addWidget(self.dashboard)

        self.calculator = Calculator(self)
        self.clock = Clock(self)

        self.image_view = ImageView(self)
        self.image_view.image_extensions = ResourceManager.instance(). \
            getFileAssociation(ResourceManager.IMAGE)

        self.wview = QWidget()
        self.vbox_view = QVBoxLayout(self.wview)
        self.vbox_view.addWidget(self.clock)
        self.vbox_view.setContentsMargins(0, 0, 0, 0)
        self.vbox_view.addWidget(self.quickview.container)
        self.vbox_view.addWidget(self.wfview.container)
        self.vbox_view.addWidget(self.image_view)
        self.vbox_view.addWidget(self.calculator)
        self.splitter.addWidget(self.wview)
        self.splitter.addWidget(self.pain_main)

        self.setCentralWidget(self.splitter)

        # create the first tab
        self.newTab(defaultpath, defaultpath_r)

        self.xcut_refresh = QShortcut(QKeySequence(Qt.Key_Escape), self)
        self.xcut_refresh.activated.connect(self.refresh)

        self.imgview_dialog = None

    def initMenuBar(self):

        menubar = self.menuBar()

        # each time the file menu is opened, invalidate
        # the vagrant menu. the vagrant menu results are cached
        # until the file menu is closed -- increasing resonse time
        self.invalidate_vagrant_menu = True

        self.file_menu = menubar.addMenu("File")
        self.file_menu.aboutToShow.connect(self.onAboutToShowFileMenu)

        act = self.file_menu.addAction("Preferences")
        act.triggered.connect(self.openSettings)

        act = self.file_menu.addAction("Edit Preferences")
        act.triggered.connect(self.editSettings)

        act = self.file_menu.addAction("Restore Previous Session")
        act.triggered.connect(self.onRestorePreviousSession)

        self.file_menu.addSeparator()

        act = self.file_menu.addAction("Open FTP")
        act.triggered.connect(self.newFtpTabTest)

        act = self.file_menu.addAction("Open SSH")
        act.triggered.connect(self.newSshTabTest)

        self.file_menu.addSeparator()

        if Settings.instance()['cmd_vagrant']:
            self.vagrant_menu = self.file_menu.addMenu("Vagrant SSH")
            self.vagrant_menu.aboutToShow.connect(self.initVagrantMenuBar)

        self.file_menu.addSeparator()

        act = self.file_menu.addAction("Sync")
        act.triggered.connect(self.onSyncRemoteFiles)

        act = self.file_menu.addAction("Clear Watch List")
        act.triggered.connect(self.onWatchersDelete)

        # act = menu.addAction("Open SSH+FTP")
        # act.triggered.connect(self.newSshTabTest)

        # act = menu.addAction("Open AWS")
        # act.triggered.connect(self.newSshTabTest)

        self.file_menu.addAction("Open SMB")
        self.file_menu.addSeparator()
        self.file_menu.addAction("Exit")

        self.help_menu = menubar.addMenu("&?")
        # sip version, qt version, python version, application version
        about_text = ""
        v = sys.version_info
        about_text += "Version: %s\n" % (self.version)
        about_text += "Commit Date:%s\n" % (self.versiondate)
        about_text += "Build Date:%s\n" % (self.builddate)
        about_text += "Python Version: %d.%d.%d-%s\n" % (
            v.major, v.minor, v.micro, v.releaselevel)
        about_text += "Qt Version: %s\n" % QT_VERSION_STR
        about_text += "sip Version: %s\n" % SIP_VERSION_STR
        about_text += "PyQt Version: %s\n" % PYQT_VERSION_STR
        self.help_menu.addAction(
            "About", lambda: QMessageBox.about(self, "Explor", about_text))

    def onAboutToShowFileMenu(self):
        self.invalidate_vagrant_menu = True

    def initVagrantMenuBar(self):

        if not self.invalidate_vagrant_menu:
            return

        self.vagrant_menu.clear()

        for dir in getVagrantInstances():
            _, n = os.path.split(dir)
            act = self.vagrant_menu.addAction(n)
            act.triggered.connect(lambda: self.newVagrantTab(dir))
            self.invalidate_vagrant_menu = False

    def initStatusBar(self):

        statusbar = self.statusBar()

        self.sbar_lbl_nsources = QLabel()  #watchers
        self.sbar_lbl_w_nfiles = QLabel()  #watchers
        self.sbar_lbl_p_nfiles = QLabel()  # delete me
        self.sbar_lbl_s_nfiles = QLabel()  # delete me

        statusbar.addWidget(self.sbar_lbl_nsources)
        statusbar.addWidget(self.sbar_lbl_w_nfiles)
        statusbar.addWidget(self.sbar_lbl_p_nfiles)
        statusbar.addWidget(self.sbar_lbl_s_nfiles)

    def openSettings(self):

        dlg = SettingsDialog(self)
        dlg.exec_()

    def editSettings(self):

        cmdstr = Settings.instance()['cmd_edit_text']
        path = YmlSettings.instance().path()
        proc_exec(cmdstr % (path), os.getcwd())

    def newFtpTabTest(self):

        url = "ftp://*****:*****@192.168.1.9:2121//"
        #url = "ftp://*****:*****@e-ftp-02.bbn.com//"
        p = parseFTPurl(url)
        p['hostname'] = p['hostname'].replace("/", "")

        print(p)
        try:
            print("enter")
            source = FTPSource(p['hostname'], p['port'], p['username'],
                               p['password'])
            print("exit")
        except ConnectionRefusedError as e:
            sys.stderr.write("error: %s\n" % e)
        else:
            view = self._newTab(source)

            view.chdir(p['path'])

    def newVagrantTab(self, vagrant_dir):

        job = NewVagrantTabJob(self, vagrant_dir)
        job.newSource.connect(self.newTabFromSource)
        self.dashboard.startJob(job)

    def newTabFromSource(self, src):

        self.sources.add(src)
        self.onUpdateSources()

        # todo: need a proper icon, and need
        # to update the icons whent he source changes.
        icon_path = ':/img/app_folder.png'
        if not isinstance(src, DirectorySource):
            icon_path = ':/img/app_archive.png'

        view = self._newTab(src, icon_path)
        view.chdir(src.root())

    def newSshTabTest(self):

        dlg = SshCredentialsDialog()

        dlg.setConfig({
            "hostname": "192.168.1.9",
            "port": 2222,
            "username": "******",
            "password": "******",
        })

        if not dlg.exec_():
            return

        cfg = dlg.getConfig()

        job = NewRemoteTabJob(self)
        job.newSource.connect(self.newTabFromSource)
        job.setConfig(cfg)
        self.dashboard.startJob(job)

    def newArchiveTab(self, view, path):

        name = view.split(path)[1]
        #zfs = ZipFS(view.open(path,"rb"),name)
        zfs = ZipFS(path, name)
        view = self._newTab(zfs, ':/img/app_archive.png')
        view.chdir("/")

    def newTab(self, defaultpath="~", defaultpath_r=""):
        view = self._newTab(self.source)

        view.ex_main.clearHistory()
        if defaultpath:
            view.chdir(defaultpath)
        else:
            view.chdir("~")

        if defaultpath_r:
            view.chdir_r(defaultpath_r)
        else:
            view.ex_secondary.chdir(view.pwd_r())

    def _newTab(self, source, icon_path=':/img/app_folder.png'):
        view = ExplorerView(source, self.controller, self)

        view.primaryDirectoryChanged.connect(
            lambda path: self.onDirectoryChanged(view, path))
        view.submitJob.connect(self.dashboard.startJob)
        view.openAsTab.connect(self.newArchiveTab)
        view.openRemote.connect(
            lambda model, path: self.onOpenRemote(view, model, path))
        view.directoryInfo.connect(self.onUpdateStatus)
        view.selectionChanged.connect(self.onSelectionChanged)

        self.tabview.addTab(view, QIcon(icon_path), "temp name")

        self.tabview.setTabsClosable(self.tabview.count() > 1)

        self.tabview.setCurrentWidget(view)

        self.sources.add(source)
        self.onUpdateSources()

        return view

    def showWindow(self):

        geometry = QDesktopWidget().screenGeometry()
        sw = geometry.width()
        sh = geometry.height()
        # calculate default values
        dw = int(sw * .6)
        dh = int(sh * .6)
        dx = sw // 2 - dw // 2
        dy = sh // 2 - dh // 2
        # use stored values if they exist
        cw = 0  #s.getDefault("window_width",dw)
        ch = 0  #s.getDefault("window_height",dh)
        cx = -1  #s.getDefault("window_x",dx)
        cy = -1  #s.getDefault("window_y",dy)
        # the application should start wholly on the screen
        # otherwise, default its position to the center of the screen
        if cx < 0 or cx + cw > sw:
            cx = dx
            cw = dw
        if cy < 0 or cy + ch > sh:
            cy = dy
            ch = dh
        if cw <= 0:
            cw = dw
        if ch <= 0:
            ch = dh
        self.resize(cw, ch)
        self.move(cx, cy)
        self.show()

        # somewhat arbitrary
        # set the width of the quick access view to something
        # reasonable
        lw = 200
        if cw > lw * 2:
            self.splitter.setSizes([lw, cw - lw])

    def onDirectoryChanged(self, view, path):

        index = self.tabview.indexOf(view)
        _, name = view.source.split(path)
        if name:
            self.tabview.setTabText(index, name)
        else:
            self.tabview.setTabText(index, "root")

    def onChangeDirectory(self, path):
        w = self.tabview.currentWidget()
        if not isinstance(w.source, DirectorySource):
            # open a new tab to display the given path
            # TODO consider passing a source allong with the path in the signal
            self.newTab(path)
        else:
            w.chdir(path)

    def onChangeDirectoryRight(self, path):
        w = self.tabview.currentWidget()
        if not isinstance(w.source, DirectorySource):
            # open a new tab to display the given path
            # TODO consider passing a source allong with the path in the signal
            self.newTab("~", path)
        else:
            w.chdir_r(path)

    def setTabCloseable(self, idx, bCloseable):
        """
        QTabBar *tabBar = ui->tabWidget->findChild<QTabBar *>();
        tabBar->setTabButton(0, QTabBar::RightSide, 0);
        tabBar->setTabButton(0, QTabBar::LeftSide, 0);

        tabWidget->tabBar()->tabButton(0, QTabBar::RightSide)->resize(0, 0);
        """

        btn1 = self.tabview.tabBar().tabButton(0, QTabBar.RightSide)
        btn2 = self.tabview.tabBar().tabButton(0, QTabBar.LeftSide)

        if btn1:
            btn1.setHidden(not bCloseable)

        if btn2:
            btn2.setHidden(not bCloseable)

    def getActiveSources(self):
        sources = set()

        for i in range(self.tabview.count()):
            widget = self.tabview.widget(i)
            if isinstance(widget, ExplorerView):
                mdl1 = widget.ex_main
                mdl2 = widget.ex_secondary

                sources.add(mdl1.view.source)
                sources.add(mdl2.view.source)

        return sources

    def closeInactiveSources(self):
        sources = self.getActiveSources()

        # get the set of sources that are no longer connected to a tab
        orphaned = self.sources - sources
        for src in orphaned:
            # todo: sources need to check for sources that depend on them
            # and cannot be closed until the dependencies are closed
            # add them to `sources` instead of closing

            # do not close the default source, it is needed for new tabs
            if src is not self.source:
                src.close()
            else:
                # add sources that cannot be closed to the set of sources
                sources.add(src)

        # update the set of opened sources
        self.sources = sources

        self.onUpdateSources()

    def onTabCloseRequest(self, idx):

        if 0 <= idx < self.tabview.count():
            self.tabview.removeTab(idx)
            self.tabview.setTabsClosable(self.tabview.count() > 1)

        self.closeInactiveSources()

    def refresh(self):
        w = self.tabview.currentWidget()
        w.refresh()

    def onShowSecondaryWindow(self, bShow):

        w = self.tabview.currentWidget()
        if bShow:
            w.ex_secondary.show()
            w.ex_main.showSplitButton(False)
            w.ex_secondary.showSplitButton(True)
        else:
            w.ex_secondary.hide()
            w.ex_main.showSplitButton(True)
            w.ex_secondary.showSplitButton(False)

    def onUpdateStatus(self, onLeft, nFiles):
        if onLeft:
            self.sbar_lbl_p_nfiles.setText("nfiles: %d" % nFiles)
        else:
            self.sbar_lbl_s_nfiles.setText("nfiles: %d" % nFiles)

    def onUpdateSources(self):
        if len(self.sources) == 1:
            self.sbar_lbl_nsources.setText("1 source")
        else:
            self.sbar_lbl_nsources.setText("%d sources" % len(self.sources))

    def onViewImage(self, view, path):
        """
        Open a dialog to display an image given by path for the source
        """
        path = view.realpath(path)

        # if it already exists, reuse the existing dialog
        if self.imgview_dialog is None:
            self.imgview_dialog = ImageDisplayDialog(self)
            self.imgview_dialog.finished.connect(self.onViewImageDialogClosed)
            self.imgview_dialog.setAttribute(Qt.WA_DeleteOnClose)

        self.imgview_dialog.setSource(view.source, path)
        self.imgview_dialog.show()

    def onViewImageDialogClosed(self):
        if self.imgview_dialog:
            self.imgview_dialog.setParent(None)
            self.imgview_dialog = None

    def onOpenRemote(self, tab, display, path):

        src = DirectorySource()
        view = display.view
        dtemp = src.join(Settings.instance()['database_directory'], "remote")
        src.mkdir(dtemp)

        remote_dname, remote_fname = view.split(path)
        ftemp = src.join(dtemp, remote_fname)

        with src.open(ftemp, "wb") as wb:
            view.getfo(path, wb)

        display._action_open_file_local(src, ftemp)

        self.wfctrl.addFile(ftemp, view, path)

    def onWatchersChanged(self, nfiles):

        self.sbar_lbl_w_nfiles.setText("watching: %d" % nfiles)

    def onWatchersDelete(self):
        self.wfctrl.clear()

    def onSyncRemoteFiles(self):

        self.wfctrl.onPostAll()

    def onRestorePreviousSession(self):
        base, _ = os.path.split(YmlSettings.instance().path())
        path = os.path.join(base, "session.yml")
        yml = YmlSettings(path)
        paths = yml.getKey("explor", "active_views", [])
        self.controller.restoreActiveViews(paths)

    def saveCurrentSession(self):
        paths = self.controller.stashActiveViews()
        base, _ = os.path.split(YmlSettings.instance().path())
        path = os.path.join(base, "session.yml")
        yml = YmlSettings(path)
        yml.setKey("explor", "active_views", paths)
        yml.save()
        print("saved yml session\n")

    def closeEvent(self, event):

        self.saveCurrentSession()

        print("Closing Application\n")

        super(MainWindow, self).closeEvent(event)

    def onSelectionChanged(self, src, paths):

        if len(paths) == 1:
            self.image_view.setPath(paths[0])
        else:
            self.image_view.setPath(None)