###########################################################################
# 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 tdeui import *
from tdecore import *
from tdeio 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 = TDEListView(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():
            TDEListView.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(TDEListViewItem):
    # 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):
        TDEListViewItem.__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("trinity1"))
            self.setPixmap(0,UserIcon("trinity"+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
