########################################################################### # SMBShareSelectDialog.py - Dialog for selecting an SMB share on a network# # ------------------------------ # # begin : Tue Oct 30 2004 # # copyright : (C) 2004 by Simon Edwards # # email : simon@simonzone.com # # # ########################################################################### # # # This program 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 2 of the License, or # # (at your option) any later version. # # # ########################################################################### from qt import * from kdeui import * from kdecore import * from kio import * ############################################################################ class SMBShareSelectDialog(KDialogBase): STATUS_IDLE = 0 STATUS_SEARCH_TOP_LEVEL = 1 STATUS_SEARCH = 2 STATUS_RESOLVE = 3 ######################################################################## def __init__(self,parent,name=None): super(SMBShareSelectDialog,self).__init__(parent,name,1,"",KDialogBase.Ok|KDialogBase.Cancel) self.updatinggui = False self.resize(600,400) vbox = self.makeVBoxMainWidget() hbox = QHBox(vbox) hbox.setSpacing(self.spacingHint()) tmplabel = QLabel(hbox) tmplabel.setPixmap(UserIcon("hi32-samba")) hbox.setStretchFactor(tmplabel,0) self.headinglabel = QLabel(hbox) self.headinglabel.setText(i18n("Select a network share")) hbox.setStretchFactor(self.headinglabel,1) hbox2 = QHBox(vbox) # The main treeview where the action happens. self.treeview = KListView(hbox2) self.treeview.addColumn("(hidden)") self.treeview.header().hide() self.treeview.setRootIsDecorated(True) self.connect(self.treeview,SIGNAL("expanded(QListViewItem *)"),self.slotNodeExpanded) self.connect(self.treeview,SIGNAL("selectionChanged(QListViewItem *)"),self.slotNodeSelected) self.connect(self.treeview,SIGNAL("clicked(QListViewItem *)"),self.slotClicked) self.dirlister = KDirLister() self.dirlister.setDirOnlyMode(True) self.dirlister.setAutoUpdate(False) self.dirlister.setAutoErrorHandlingEnabled(True,self) self.connect(self.dirlister,SIGNAL("newItems(const KFileItemList &)"),self.slotNewItems) self.connect(self.dirlister,SIGNAL("completed()"),self.slotDirListCompleted) self.connect(self.dirlister,SIGNAL("canceled()"),self.slotDirListCanceled) self.connect(self.dirlister,SIGNAL("redirection(const KURL &,const KURL &)"),self.slotDirListRedirection) self.enableButtonOK(False) # The "Connect as" part widget = QWidget(hbox2) grid = QGridLayout(widget,6,4,KDialog.spacingHint()) grid.setRowStretch(5,1) tmplabel = QLabel(widget) tmplabel.setPixmap(UserIcon("hi16-password")) grid.addWidget(tmplabel,0,0) self.connectaslabel = QLabel(widget) self.connectaslabel.setText("Connect to 'XXX' as:") grid.addMultiCellWidget(self.connectaslabel,0,0,1,3) self.guestradio = QRadioButton(widget) self.guestradio.setChecked(True) grid.addWidget(self.guestradio,1,1) tmplabel = QLabel(widget) tmplabel.setText(i18n("Guest")) grid.addWidget(tmplabel,1,2) self.connect(self.guestradio,SIGNAL("stateChanged(int)"),self.slotGuestRadioClicked) self.userradio = QRadioButton(widget) grid.addWidget(self.userradio,2,1) tmplabel = QLabel(widget) tmplabel.setText(i18n("Username:")) grid.addWidget(tmplabel,2,2) self.connect(self.userradio,SIGNAL("stateChanged(int)"),self.slotUserRadioClicked) self.usernameedit = KLineEdit(widget) grid.addWidget(self.usernameedit,2,3) self.connect(self.usernameedit,SIGNAL("textChanged(const QString &)"),self.slotUsernameChanged) tmplabel = QLabel(widget) tmplabel.setText(i18n("Password:")) grid.addWidget(tmplabel,3,2) self.passwordedit = KLineEdit(widget) grid.addWidget(self.passwordedit,3,3) self.reconnectbutton = KPushButton(i18n("Reconnect now"),widget) grid.addMultiCellWidget(self.reconnectbutton,4,4,1,3) self.connect(self.reconnectbutton,SIGNAL("clicked()"),self.slotReconnectClicked) self.dirlistertimer = None ######################################################################## def choose(self,currenturl): self.lookupqueue = [] self.selecteditem = None self.treeview.clear() self.url_to_list_item_map = {} # Fill the first level root_url = KURL("smb:/") self.rootitem = SMBShareListViewItem(self.treeview, i18n("Network Neighbourhood"), root_url, self) self.searchurl = currenturl self._updateConnectGUI() self.enableButtonOK(False) self._openDefaultURL() self.spintimerid = self.startTimer(250) self.exec_loop() self.stopResolve() self.killTimer(self.spintimerid) if self.result()==self.Accepted: currenturl = self.selecteditem.getURL() self.url_to_list_item_map = None return currenturl ######################################################################## def _openDefaultURL(self): if self.searchurl is not None: rc = self.rootitem.selectURL(self.searchurl) if rc==self.rootitem.OPEN_SUCCESS: self.currenturl = self.searchurl self.searchurl = None self.enableButtonOK(True) elif rc==self.rootitem.OPEN_FAIL or rc==self.rootitem.OPEN_SUCCESS_INVALID: self.searchurl = None ######################################################################## def stopResolve(self): if self.dirlistertimer is not None: self.killTimer(self.dirlistertimer) self.dirlister.stop() for item in self.lookupqueue: item.cancelResolve() self.lookupqueue = [] self.searchurl = None # Stop trying to open this URL too. ######################################################################## def setOpen(self,item,open): if item.isResolved(): KListView.setOpen(self.treeview,item,open) else: item.startResolve(True) ######################################################################## def appendToResolveQueue(self,item): if item not in self.lookupqueue: self.lookupqueue.append(item) self._startDirLister() return True else: return False ######################################################################## def slotNodeExpanded(self,item): self.setOpen(item,True) ######################################################################## def slotClicked(self): if self.treeview.selectedItem() is None: self.selecteditem = None self._updateConnectGUI() self.enableButtonOK(False) ######################################################################## def slotNodeSelected(self,item): self.selecteditem = item self._updateConnectGUI() self.enableButtonOK(item.getLevel()==item.LEVEL_DIR) if not self.selecteditem.isResolved(): self.selecteditem.startResolve(False) ######################################################################## def slotNewItems(self,items): for entry in items: newitem = SMBShareListViewItem(self.lookupqueue[0], unicode(entry.name()), KURL(entry.url()), self) self.url_to_list_item_map[unicode(entry.url().prettyURL())] = newitem # Notice how I copied the KURL object and QString (to a python string) ######################################################################## def slotDirListCompleted(self): item = self.lookupqueue[0] item.setBusyIcon(False) del self.lookupqueue[0] item.resolveComplete() self._startDirLister() self._openDefaultURL() ######################################################################## def slotDirListCanceled(self): self.stopResolve() ######################################################################## def slotDirListRedirection(self,oldUrl,newUrl): list_item = self.url_to_list_item_map[unicode(oldUrl.prettyURL())] list_item.setURL(KURL(newUrl)) # The copy is important. # Reselect the selected node. (This will force a refresh). if self.selecteditem is not None: self.updatinggui = True self.slotNodeSelected(self.selecteditem) self.updatinggui = False ######################################################################## def slotUsernameChanged(self,newtext): self.reconnectbutton.setEnabled(self.usernameedit.text()!="") ######################################################################## def slotReconnectClicked(self): if self.updatinggui: return self.updatinggui = True if self.selecteditem is None: # Sanity check. return # The user wants to change how we connect to this remote machine. machineitem = self.selecteditem.getMachineItem() if machineitem is None: return # Shouldn't happen. self.stopResolve() # Grab the URL object before we delete the listviewitem that holds it. selectedurl = self.selecteditem.getURL() # Close up the machine item and remove the items under the machine item. machineitem.unresolve() # Set the username/password for the machine item. if self.guestradio.isChecked(): machineitem.getURL().setUser(QString.null) machineitem.getURL().setPass(QString.null) selectedurl.setUser(QString.null) selectedurl.setPass(QString.null) else: machineitem.getURL().setUser(self.usernameedit.text()) machineitem.getURL().setPass(self.passwordedit.text()) selectedurl.setUser(self.usernameedit.text()) selectedurl.setPass(self.passwordedit.text()) self.selecteditem = None self._updateConnectGUI() self.searchurl = selectedurl self._openDefaultURL() self.updatinggui = False ######################################################################## def _startDirLister(self): if self.dirlistertimer is None: # Check the URL lister queue the next the event loop runs. # Don't get all "recursed up"! self.dirlistertimer = self.startTimer(0) ######################################################################## def timerEvent(self,event): KDialogBase.timerEvent(self,event) if self.spintimerid==event.timerId(): # Spin the current folder icon if len(self.lookupqueue)!=0: self.lookupqueue[0].setBusyIcon(True) elif event.timerId()==self.dirlistertimer: self.killTimer(self.dirlistertimer) self.dirlistertimer = None if self.dirlister.isFinished(): if len(self.lookupqueue)!=0: self.dirlister.openURL(self.lookupqueue[0].getURL()) ######################################################################## def slotGuestRadioClicked(self,state): if self.updatinggui: return self.updatinggui = True if self.selecteditem is None: return if state==QButton.Off: self.guestradio.setChecked(True) self.userradio.setChecked(False) self.passwordedit.setEnabled(False) self.usernameedit.setEnabled(False) selectedurl = self.selecteditem.getURL() self.reconnectbutton.setEnabled(unicode(selectedurl.user())!="") self.updatinggui = False ######################################################################## def slotUserRadioClicked(self,state): if self.updatinggui: return self.updatinggui = True if state==QButton.Off: self.userradio.setChecked(True) self.guestradio.setChecked(False) self.passwordedit.setEnabled(True) self.usernameedit.setEnabled(True) username = unicode(self.usernameedit.text()) password = unicode(self.passwordedit.text()) selectedurl = self.selecteditem.getURL() if username!="" and password!="" and \ ((unicode(selectedurl.user())!=username) or (unicode(selectedurl.pass_())!=password)): self.reconnectbutton.setEnabled(True) else: self.reconnectbutton.setEnabled(False) self.updatinggui = False ######################################################################## def _updateConnectGUI(self): if self.selecteditem is not None: selectedurl = self.selecteditem.getURL() self.guestradio.setEnabled(True) self.userradio.setEnabled(True) self.usernameedit.setEnabled(selectedurl.hasUser()) self.passwordedit.setEnabled(selectedurl.hasUser()) self.connectaslabel.setText(i18n("Connect to '%1' as:").arg(selectedurl.host())) if selectedurl.hasUser(): self.guestradio.setChecked(False) self.userradio.setChecked(True) self.usernameedit.setText(selectedurl.user()) self.passwordedit.setText(selectedurl.pass_()) else: self.guestradio.setChecked(True) self.userradio.setChecked(False) self.passwordedit.setText("") self.usernameedit.setText("") self.reconnectbutton.setEnabled(False) else: self.guestradio.setChecked(True) self.userradio.setChecked(False) self.guestradio.setEnabled(False) self.userradio.setEnabled(False) self.passwordedit.setEnabled(False) self.usernameedit.setEnabled(False) self.connectaslabel.setText(i18n("Connect to 'machine' as:")) self.guestradio.setChecked(True) self.userradio.setChecked(False) self.passwordedit.setText("") self.usernameedit.setText("") self.reconnectbutton.setEnabled(False) ############################################################################ class SMBShareListViewItem(KListViewItem): # Return codes for selectURL() OPEN_SUCCESS = 1 OPEN_SUCCESS_INVALID = 2 OPEN_FAIL = 0 OPEN_BUSY = 3 # Node types. LEVEL_ROOT = 0 LEVEL_WORKGROUP = 1 LEVEL_MACHINE = 2 LEVEL_DIR = 3 # and deeper. ######################################################################## def __init__(self,parentitem,name,url,smbdialog): KListViewItem.__init__(self,parentitem,name) if not isinstance(parentitem,SMBShareListViewItem): self._setIcon(0) self.setSelectable(False) else: self._setIcon(parentitem.depth()+1) self.setSelectable(parentitem.getLevel()>=self.LEVEL_WORKGROUP) self.setExpandable(True) if url.hasPath() and url.path(-1)!="/": parts = [x for x in unicode(url.path(-1)).split("/") if x!=""] self.component = parts[-1].lower() elif url.hasHost(): self.component = unicode(url.host()).lower() else: self.component = None self.smbdialog = smbdialog self.resolved = False self.url = url self.autoopen = False self.animationcounter = 0 ######################################################################## def getURL(self): return self.url ######################################################################## def setURL(self,url): self.url = url ######################################################################## def getComponent(self): return self.component ######################################################################## def isResolved(self): return self.resolved ######################################################################## def startResolve(self,autoopen): if self.smbdialog.appendToResolveQueue(self): self.setBusyIcon(True) self.autoopen = self.autoopen or autoopen ######################################################################## def cancelResolve(self): self.setBusyIcon(False) self.autoopen = False self.resolved = False while self.childCount()!=0: self.takeItem(self.firstChild()) self.setOpen(False) ######################################################################## def unresolve(self): self.cancelResolve() ######################################################################## def getMachineItem(self): if self.getLevel()<=self.LEVEL_WORKGROUP: return None elif self.getLevel()==self.LEVEL_DIR: return self.parent().getMachineItem() else: return self ######################################################################## def _setIcon(self,depth): if depth==self.LEVEL_ROOT or depth==self.LEVEL_WORKGROUP: self.setPixmap(0,SmallIcon("network")) elif depth==self.LEVEL_MACHINE: self.setPixmap(0,SmallIcon("network_local")) else: self.setPixmap(0,SmallIcon("folder")) ######################################################################## def setBusyIcon(self,on): if on: self.setPixmap(0,UserIcon("kde1")) self.setPixmap(0,UserIcon("kde"+str(self.animationcounter+1))) self.animationcounter += 1 self.animationcounter %= 6 else: self._setIcon(self.depth()) ######################################################################## def resolveComplete(self): self.resolved = True if self.childCount()==0: self.setExpandable(False) else: if self.autoopen: self.setOpen(True) ######################################################################## def getLevel(self): if self.depth()>self.LEVEL_DIR: return self.LEVEL_DIR else: return self.depth() ######################################################################## # This is one of the more nasty pieces of code. It tries to select a given # URL in the treeview. Opening and resolving the contents of URLs as neccessary # while at the same time trying not have list everything on the network. # Another wrinkle is that the treeview contains a level of workgroups while # a given URL omits the workgroup a jumps directly to the machine name. def selectURL(self,targeturl): path = unicode(targeturl.path(-1)) parts = [x for x in path.split("/") if x!=""] if targeturl.hasHost(): tmp = [targeturl.host()] tmp.extend(parts) parts = tmp if self.getLevel()==self.LEVEL_ROOT: # Root item. # We should first resolve our contents. the Workgroups. if not self.resolved: self.startResolve(True) return self.OPEN_BUSY else: if len(parts)==0: # The URL is really short, and is not selectable. # So we just say that we couldn't resolve/select it. return self.OPEN_SUCCESS_INVALID else: # OK, the url has some more components. Ask each of the Workgroup items # to help resolve it. kid = self.firstChild() while kid is not None: rc = kid.selectURL(targeturl) if rc==self.OPEN_SUCCESS or rc==self.OPEN_SUCCESS_INVALID: kid.setOpen(True) return rc elif rc==self.OPEN_BUSY: return rc kid = kid.nextSibling() return self.OPEN_FAIL elif self.getLevel()==self.LEVEL_WORKGROUP: # Workgroup level if not self.resolved: self.startResolve(False) return self.OPEN_BUSY else: # Find a child named after the next part of the URL path. kid = self.firstChild() partname = parts[0].lower() while kid is not None: if kid.getComponent()==partname: self.setOpen(True) return kid.selectURL(targeturl) kid = kid.nextSibling() return self.OPEN_FAIL elif self.getLevel()==self.LEVEL_MACHINE: # Machine level if len(parts)==1: # The URL is successfully resolved but is not selectable! return self.OPEN_SUCCESS_INVALID else: # Share level if len(parts)==self.depth()-1: self.smbdialog.treeview.setSelected(self,True) return self.OPEN_SUCCESS if not self.resolved: self.startResolve(True) return self.OPEN_BUSY else: # Find a child item that matches the next part of the URL path. kid = self.firstChild() partname = parts[self.depth()-1].lower() while kid is not None: if kid.getComponent()==partname: return kid.selectURL(targeturl) kid = kid.nextSibling() return self.OPEN_FAIL