fclient-commit Mailing List for fclient (Page 37)
Status: Pre-Alpha
Brought to you by:
jurner
You can subscribe to this list here.
| 2007 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(23) |
Nov
(54) |
Dec
|
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2008 |
Jan
(17) |
Feb
(209) |
Mar
(63) |
Apr
(31) |
May
(7) |
Jun
(39) |
Jul
(390) |
Aug
(122) |
Sep
(6) |
Oct
|
Nov
|
Dec
|
|
From: <jU...@us...> - 2007-11-10 11:25:36
|
Revision: 62
http://fclient.svn.sourceforge.net/fclient/?rev=62&view=rev
Author: jUrner
Date: 2007-11-10 03:25:39 -0800 (Sat, 10 Nov 2007)
Log Message:
-----------
for testing.. will be removed later
Added Paths:
-----------
trunk/fclient/download/
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-08 13:21:13
|
Revision: 61
http://fclient.svn.sourceforge.net/fclient/?rev=61&view=rev
Author: jUrner
Date: 2007-11-08 05:21:17 -0800 (Thu, 08 Nov 2007)
Log Message:
-----------
some more user options ++ item tips ++ fixed status for RequestInfo
Modified Paths:
--------------
trunk/fclient/fclient_widgets/download_widget.py
Modified: trunk/fclient/fclient_widgets/download_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/download_widget.py 2007-11-08 13:18:59 UTC (rev 60)
+++ trunk/fclient/fclient_widgets/download_widget.py 2007-11-08 13:21:17 UTC (rev 61)
@@ -20,6 +20,7 @@
from fclient_lib.qt4ex import settingsbase
+from fclient_lib.qt4ex.ctrls import areatips
from fclient_lib.qt4ex.ctrls import progressbarwrap
from fclient_lib.qt4ex.ctrls import treewidgetwrap
@@ -29,8 +30,6 @@
del parentdir
#<-- rel import hack
-
-import thread
import time
from PyQt4 import QtCore, QtGui
#*****************************************************************************
@@ -55,7 +54,12 @@
('ColorProgressBarStopped', ['toString', 'gray']),
('ColorProgressBarStopped', ['toString', 'white']),
+ ('MaxSimultaniousDownloads', ['toInt', 2]),
+ ('MaxNewDownloadsPerHop', ['toInt', 100]),
+
('ClearCompletedDownloads', ['toBool', False]),
+ ('ItemTipsShow', ['toBool', True]),
+ ('ItemTipsDelay', ['toInt', areatips.DEFAULT_SHOW_DELAY])
)
#*****************************************************************************
@@ -256,16 +260,12 @@
connectionName='',
directory=None,
uris=None,
- maxQueuedItemsPerHop=200,
- maxSimultaniousDownloads=2,
cfg=None
):
"""
@param parent: (QWidget) parent or None
@param directory: (str) directory to sownload items to or None to use default directory
@param connectionName: name of the connection to the node
- @param maxQueuedItemsPerHop: maximum number of items that can be e added in one hop
- @param maxSimultaniousDownloads: maximim number of simultaneous downloads
@param cfg: (configConfig) instance or None
"""
@@ -281,13 +281,15 @@
}
self._fcpClient = None
self._isCreated = False
- self._lock = thread.allocate_lock()
- self._maxQueuedItemsPerHop = maxQueuedItemsPerHop #TODO: move to settings
- self._maxSimultaniousDownloads = maxSimultaniousDownloads #TODO: move to settings
+ self._itemTips = areatips.ItemTips(self)
self._strings = None
-
self._userSettings = UserSettings()
+
+ # setup item tips
+ self._itemTips.setEnabled(self._userSettings['ItemTipsShow'])
+ self._itemTips.setShowDelay(self._userSettings['ItemTipsDelay'])
+
# setup tree
self.setSelectionMode(self.ContiguousSelection)
header = self.header()
@@ -367,22 +369,26 @@
progress = item.progressWidget(self)
status = DownloadItem.StatusError
+
+ item.setRequestStatus(status, self._strings.itemStatus[status])
+ item.setRequestError(event, params)
- progress.setRange(0, 1)
- progress.setValue(1)
+ # get rid of progress pending indicator if necessary
+ if progress.maximum() == progress.minimum() == 0:
+ progress.setRange(0, 1)
+ progress.setValue(0)
progress.setColors(
colorBar=self._userSettings['ColorProgressBarError'],
colorBg=self._userSettings['ColorProgressBgError']
)
- item.setRequestStatus(status, self._strings.itemStatus[status])
- item.setRequestError(event, params)
-
+
def handleFcpClientIdle(self, event, params):
# check if there are sownloads queued
n = 0
- while self._downloads['DownloadQueue'] and n < self._maxQueuedItemsPerHop:
+ maxNewDls = self._userSettings['MaxNewDownloadsPerHop']
+ while self._downloads['DownloadQueue'] and n < maxNewDls:
n += 1
parent, uri = self._downloads['DownloadQueue'].pop(0)
@@ -418,7 +424,7 @@
items[status].append(item)
# start items with info requested
- itemsToBeStarted = self._maxSimultaniousDownloads - itemsBusy
+ itemsToBeStarted = self._userSettings['MaxSimultaniousDownloads'] - itemsBusy
if itemsToBeStarted > 0:
def sortf(item1, item2):
return cmp(item1.requestInitTime(), item2.requestInitTime())
@@ -443,6 +449,7 @@
item = pendingItems.pop(0)
progress = progressbarwrap.ProgressBarEx(self)
+ status = DownloadItem.StatusRequestInfo
uri = item.requestUri()
progress.setRange(0, 0)
@@ -452,6 +459,7 @@
colorBg=self._userSettings['ColorProgressBgRequestInfo']
)
item.setProgressWidget(self, progress)
+ item.setRequestStatus(status, self._strings.itemStatus[status])
identifier = self._fcpClient.clientRequestInfo(uri)
self._downloads['Downloads'][identifier] = item
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-08 13:18:54
|
Revision: 60
http://fclient.svn.sourceforge.net/fclient/?rev=60&view=rev
Author: jUrner
Date: 2007-11-08 05:18:59 -0800 (Thu, 08 Nov 2007)
Log Message:
-----------
fixed typo
Modified Paths:
--------------
trunk/fclient/fclient_lib/qt4ex/ctrls/areatips.py
Modified: trunk/fclient/fclient_lib/qt4ex/ctrls/areatips.py
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/ctrls/areatips.py 2007-11-08 11:13:54 UTC (rev 59)
+++ trunk/fclient/fclient_lib/qt4ex/ctrls/areatips.py 2007-11-08 13:18:59 UTC (rev 60)
@@ -300,7 +300,7 @@
"""adjusts the show delay
@param n milliseconds to delay the popup of the tooltip
"""
- return self.areaTip.setShowDelay(delay)
+ return self.areaTip.setShowDelay(n)
#****************************************************************************
#
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-08 11:13:49
|
Revision: 59
http://fclient.svn.sourceforge.net/fclient/?rev=59&view=rev
Author: jUrner
Date: 2007-11-08 03:13:54 -0800 (Thu, 08 Nov 2007)
Log Message:
-----------
continued working on downloads widget
Modified Paths:
--------------
trunk/fclient/fclient_widgets/download_widget.py
Modified: trunk/fclient/fclient_widgets/download_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/download_widget.py 2007-11-08 11:12:56 UTC (rev 58)
+++ trunk/fclient/fclient_widgets/download_widget.py 2007-11-08 11:13:54 UTC (rev 59)
@@ -19,7 +19,7 @@
from fclient_lib.pyex import namespace, numbers
-#from fclient_lib.qt4ex import settingsbase
+from fclient_lib.qt4ex import settingsbase
from fclient_lib.qt4ex.ctrls import progressbarwrap
from fclient_lib.qt4ex.ctrls import treewidgetwrap
@@ -36,7 +36,33 @@
#*****************************************************************************
#
#*****************************************************************************
+class UserSettings(settingsbase.SettingsBase):
+ """Settings for the download widget"""
+
+ #TODO: impl dynamic setting of key for multiple widgets per app
+ KEY_SETTINGS = 'DownloadsWidgetUser'
+ SETTINGS = (
+ ('ColorProgressBarRequestInfo', ['toString', 'cyan']),
+ ('ColorProgressBgRequestInfo', ['toString', 'white']),
+ ('ColorProgressBarRequestInfoComplete', ['toString', 'darkCyan']),
+ ('ColorProgressBgRequestInfoComplete', ['toString', 'white']),
+ ('ColorProgressBarDownloading', ['toString', 'blue']),
+ ('ColorProgressBgDownloading', ['toString', 'white']),
+ ('ColorProgressBarDownloadComplete', ['toString', 'green']),
+ ('ColorProgressBgDownloadComplete', ['toString', 'white']),
+ ('ColorProgressBarError', ['toString', 'red']),
+ ('ColorProgressBgError', ['toString', 'white']),
+ ('ColorProgressBarStopped', ['toString', 'gray']),
+ ('ColorProgressBarStopped', ['toString', 'white']),
+
+ ('ClearCompletedDownloads', ['toBool', False]),
+ )
+
+#*****************************************************************************
+#
+#*****************************************************************************
class DownloadItem(QtGui.QTreeWidgetItem):
+ """Tree item"""
# default indices
IndexName = 0
@@ -57,81 +83,138 @@
StatusRequestInfoComplete = 0x4
StatusDownloading = 0x8
StatusDownloadComplete = 0x10
+
+ StatusStopped = 0x1000
+ StatusError = 0x20000
- StatusError = 0x10000
-
StatusMaskBusy = StatusRequestInfo | StatusDownloading
+
-
-
-
def __init__(self, parent, requestIdentifier, uri):
+ """
+ @param parent: parent of the item
+ @param requestIdentifier: (str) request identifier
+ @param uri: (str) uri to download
+ """
+
QtGui.QTreeWidgetItem.__init__(self, parent)
+ self._requestError = None
self._requestIdentifier = requestIdentifier
self._requestStatus = self.StatusNone
self._requestInitTime = time.time()
self._uri = uri
-
+
+ def setRequestError(self, event, params):
+ """If an error occures, sets info about the error
+ @param event: event that triggered the error
+ @param params: params passed along with the event
+ """
+ self._requestError = (event, params)
+
+ def requestError(self):
+ """Return information about the last error encountered or None"""
+ return self._requestError
+
def setRequestIdentifier(self, identifier):
+ """Sets the request identifier of the item
+ @param requestIdentifier: (str) request identifier
+ """
self._requestIdentifier = identifier
def requestIdentifier(self):
+ """Returns the request identifier of the item"""
return self._requestIdentifier
def requestInitTime(self):
+ """Returns the time the request was initialized
+ @return: (float) time
+ """
return self._requestInitTime
def setRequestMimeType(self, mimeType):
+ """Sets the mime type of the item
+ @param mimeType: (str) mime type
+ """
self.setText(self.IndexMimeType, mimeType)
def requestMimeType(self):
+ """Returns the mime type of the item"""
return self.text(self.IndexMimeType)
def setRequestName(self, name):
+ """Sets the request name of the item
+ @param name: (str) name
+ """
self.setText(self.IndexName, name)
def requestName(self):
+ """Returns the request name of the item
+ """
return self.text(self.IndexName)
def setRequestPriority(self, priority):
+ """Sets the request priority of the item
+ @param prority: (FcpClient.Priority)
+ """
self.setText(self.IndexPriority, priority)
def requestPriority(self):
+ """Returns the request priority of the item"""
return self.text(self.IndexPriority)
def setProgressWidget(self, tree, widget):
+ """Associates a progress widget to the item
+ @param tree: (QTreeWidget)
+ @param widget: (QProgressBar)
+ """
tree.setItemWidget(self, self.IndexProgress, widget)
def progressWidget(self, tree):
+ """Returns the progress widget associated to the item"""
return tree.itemWidget(self, self.IndexProgress)
def setRequestStatus(self, status, text=''):
+ """Sets the request status of the item
+ @param status: one of the Status* consts
+ @param text: status text to display
+ """
self._requestStatus = status
self.setText(self.IndexStatus, text)
def requestStatus(self):
+ """Returns the request status of the item
+ @return: (int) one of the Status* consts
+ """
return self._requestStatus
def setRequestSize(self, size):
+ """Sets the request size of the item
+ @param size: (str) size
+ """
self.setText(self.IndexSize, size)
def requestSize(self):
+ """Returns the request size of the item"""
return self.text(self.IndexSize)
def setRequestUri(self, uri):
+ """Sets the request uri of the item
+ @param uri: (str) uri
+ """
self._uri = uri
def requestUri(self):
+ """Returns the request uri of the item"""
return self._uri
#*****************************************************************************
#
#*****************************************************************************
class DownloadWidgetStrings(QtCore.QObject):
-
-
+ """Strings for the download widget"""
+
def __init__(self, parent):
QtCore.QObject.__init__(self, parent)
@@ -155,6 +238,7 @@
DownloadItem.StatusDownloading: self.trUtf8('Loading'),
DownloadItem.StatusDownloadComplete: self.trUtf8('Complete'),
+ DownloadItem.StatusStopped: self.trUtf8('Stopped'),
DownloadItem.StatusError: self.trUtf8('Error'),
}
@@ -202,6 +286,8 @@
self._maxSimultaniousDownloads = maxSimultaniousDownloads #TODO: move to settings
self._strings = None
+ self._userSettings = UserSettings()
+
# setup tree
self.setSelectionMode(self.ContiguousSelection)
header = self.header()
@@ -212,7 +298,7 @@
self.connect(
self,
QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'),
- self.handleContextMenu
+ self.handleCustomContextMenu
)
@@ -227,14 +313,14 @@
def showEvent(self, event):
if not self._isCreated:
self._isCreated = True
- self._fcpClient = self._cfg.fcpClientManager.newClient(self._connectionName, self.handleClientConnected)
+ self._fcpClient = self._cfg.fcpClientManager.newClient(self._connectionName, self.handleFcpClientConnected)
#############################################################
##
## handlers for Qt events
##
#############################################################
- def handleContextMenu(self):
+ def handleCustomContextMenu(self):
pass
@@ -244,16 +330,19 @@
## handlers for Fcp events
##
#############################################################
- def handleClientConnected(self, event, params):
+ def handleFcpClientConnected(self, event, params):
"""
"""
self._fcpEvents = (
- (self._fcpClient.events.Idle, self.handleClientIdle),
- (self._fcpClient.events.ClientDisconnected, self.handleClientDisconnected),
+ (self._fcpClient.events.Idle, self.handleFcpClientIdle),
+ (self._fcpClient.events.ClientDisconnected, self.handleFcpClientDisconnected),
- (self._fcpClient.events.TestDDAComplete, self.handleTestDDAComplete),
- (self._fcpClient.events.ClientGetInfo, self.handleClientGetInfo),
- (self._fcpClient.events.ClientGetInfoProgress, self.handleClientGetInfoProgress),
+ (self._fcpClient.events.TestDDAComplete, self.handleFcpClientTestDDAComplete),
+ (self._fcpClient.events.ClientRequestInfo, self.handleFcpClientRequestInfo),
+ (self._fcpClient.events.ClientRequestInfoProgress, self.handleFcpClientRequestInfoProgress),
+
+ (self._fcpClient.events.GetFailed, self.handleFcpClientGetFailed),
+ (self._fcpClient.events.ProtocolError, self.handleFcpClientProtocolError),
)
# take care not to connect twice
for event, observer in self._fcpEvents:
@@ -265,9 +354,32 @@
self._fcpClient.testDDA(directory, wantWriteDirectory=True)
-
- def handleClientIdle(self, event, msg):
+ def handleFcpClientDisconnected(self, event, params):
+ """
+ """
+
+ #TODO: how to provide extra information to the user?
+ def handleFcpClientGetFailed(self, event, params):
+ identifier = params['Identifier']
+ item = self._downloads['Downloads'].get(identifier, None)
+ if item is not None:
+ del self._downloads['Downloads'][identifier]
+ progress = item.progressWidget(self)
+ status = DownloadItem.StatusError
+
+ progress.setRange(0, 1)
+ progress.setValue(1)
+ progress.setColors(
+ colorBar=self._userSettings['ColorProgressBarError'],
+ colorBg=self._userSettings['ColorProgressBgError']
+ )
+ item.setRequestStatus(status, self._strings.itemStatus[status])
+ item.setRequestError(event, params)
+
+
+ def handleFcpClientIdle(self, event, params):
+
# check if there are sownloads queued
n = 0
while self._downloads['DownloadQueue'] and n < self._maxQueuedItemsPerHop:
@@ -287,6 +399,9 @@
# check how many downloads are currently running
+
+ #TODO: bit sloppy coding here. We know how max downloads to start,
+ # so should be not too hard to keep the lists short
items = {
DownloadItem.StatusPending: [],
DownloadItem.StatusRequestInfoComplete: [],
@@ -329,46 +444,33 @@
item = pendingItems.pop(0)
progress = progressbarwrap.ProgressBarEx(self)
uri = item.requestUri()
-
-
+
progress.setRange(0, 0)
progress.setValue(0)
+ progress.setColors(
+ colorBar=self._userSettings['ColorProgressBarRequestInfo'],
+ colorBg=self._userSettings['ColorProgressBgRequestInfo']
+ )
item.setProgressWidget(self, progress)
- identifier = self._fcpClient.clientGetInfo(uri)
+ identifier = self._fcpClient.clientRequestInfo(uri)
self._downloads['Downloads'][identifier] = item
-
- def handleClientDisconnected(self, event, params):
- """
- """
- def handleTestDDAComplete(self, event, params):
- """
- """
- directory = params['Directory']
- readAllowed = params.get('WriteDirectoryAllowed')
- writeAllowed = params.get('WriteDirectoryAllowed')
+ def handleFcpClientProtocolError(self, event, params):
+ identifier = params.get('Identifier', None)
+ if identifier is None:
+ #TDO: handle error
+ pass
+ else:
+ item = self._downloads['Downloads'].get(identifier, None)
+ if item is not None:
+ self.handleFcpClientGetFailed(event, params)
+
- # check if there are items to be donloaded for the directory
- downloads = self._downloads['DownloadDirectories'].get(directory, None)
- if downloads is not None:
- del self._downloads['DownloadDirectories'][directory] # ???
-
- if downloads:
- if writeAllowed == self._fcpClient.FcpTrue:
- parent, uris = downloads
- for uri in uris:
- self.download(uri, parent=parent)
-
- else:
- pass
- #TODO: write access denied, error
-
+ def handleFcpClientRequestInfo(self, event, params):
- def handleClientGetInfo(self, event, params):
-
identifier = params['Identifier']
item = self._downloads['Downloads'].get(identifier, None)
if item is not None:
@@ -384,13 +486,18 @@
else:
dataLength = numbers.format_num_bytes(dataLength)
+ progress = item.progressWidget(self)
+ progress.setColors(
+ colorBar=self._userSettings['ColorProgressBarRequestInfoComplete'],
+ colorBg=self._userSettings['ColorProgressBgRequestInfoComplete']
+ )
+
item.setRequestStatus(status, self._strings.itemStatus[status])
item.setRequestMimeType(mimeType)
item.setRequestSize(dataLength)
-
-
- def handleClientGetInfoProgress(self, event, params):
+
+ def handleFcpClientRequestInfoProgress(self, event, params):
identifier = params['Identifier']
item = self._downloads['Downloads'].get(identifier, None)
@@ -403,6 +510,29 @@
progress.setRange(0, required)
progress.setValue(succeeded)
+
+ def handleFcpClientTestDDAComplete(self, event, params):
+ """
+ """
+ directory = params['Directory']
+ readAllowed = params.get('WriteDirectoryAllowed')
+ writeAllowed = params.get('WriteDirectoryAllowed')
+
+ # check if there are items to be donloaded for the directory
+ downloads = self._downloads['DownloadDirectories'].get(directory, None)
+ if downloads is not None:
+ del self._downloads['DownloadDirectories'][directory] # ???
+
+ if downloads:
+ if writeAllowed == self._fcpClient.FcpTrue:
+ parent, uris = downloads
+ for uri in uris:
+ self.download(uri, parent=parent)
+
+ else:
+ pass
+ #TODO: write access denied, error
+
#######################################################
##
## methods
@@ -434,7 +564,7 @@
CHK@AbQOZRSJcgXnKOI16D2hF8qkjxp3cl1u72bkL2KS29E,jjQlvojCkJJgzI2WgWj5rMsmUd935zjGChiV-j937X0,AAIC--8/snow_002%20%2814%29.jpg
CHK@1ZW0fOpB7RmUFqSO2TtAOyfFm1uJ6~JcWuPESiYwtc4,aCDkI~JCr3Mb8s7rPBgmalPqzQEFdovlF~DgDRkGWuE,AAIC--8/snow_002%20%2815%29.jpg
CHK@a8jHCl0TkwVAcTdYOezd-FtIWTLIFhjrWeHXVcSjRPk,4rIwDZE~-Gp~B5tjDSAcSpn~tLgMmnMUFeikXP4f7y0,AAIC--8/snow_002%20%2816%29.jpg
-CHK@Jg~F~~WbKu8AlBgmwkonWGQNzZFUholE2CkGnyt23kk,Gj7JtnR97EIRvabG8m6Xp1dUAqiP6qD4J5FJRN9waMQ,AAIC--8/snow_002%20%2836%29.jpg'''
+CHK@[I AM INVALID] Jg~F~~WbKu8AlBgmwkonWGQNzZFUholE2CkGnyt23kk,Gj7JtnR97EIRvabG8m6Xp1dUAqiP6qD4J5FJRN9waMQ,AAIC--8/should-error.test'''
TestUris = TestUris.split('\n')
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-08 11:12:52
|
Revision: 58
http://fclient.svn.sourceforge.net/fclient/?rev=58&view=rev
Author: jUrner
Date: 2007-11-08 03:12:56 -0800 (Thu, 08 Nov 2007)
Log Message:
-----------
protocol errors in relation to requests are now passed to the caller ++ some renames
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-07 17:35:34 UTC (rev 57)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-08 11:12:56 UTC (rev 58)
@@ -146,6 +146,8 @@
'ClientConnected',
'ClientDisconnected',
+ 'ProtocolError',
+
# config related events
'ConfigData',
'NodeData',
@@ -164,8 +166,8 @@
'TestDDAComplete',
'IdentifierCollision',
- 'ClientGetInfo',
- 'ClientGetInfoProgress',
+ 'ClientRequestInfo',
+ 'ClientRequestInfoProgress',
'DataFound',
'GetFailed',
@@ -319,7 +321,7 @@
class IdentifierPrefix:
"""Special purpose identifier prefixes"""
- ClientGetInfo = 'ClientGetInfo::'
+ ClientRequestInfo = 'ClientRequestInfo::'
class InsertError(Exception):
"""All insert errors supported by the client"""
@@ -797,8 +799,14 @@
self.events.ClientDisconnect({'DisconnectReason': DisconnectReason.Shutdown})
return True
- raise self.ProtocolError(msg)
+ identifier = msg.get('Identifier', None)
+ if identifier is None:
+ #TODO: check how to handle this
+ raise self.ProtocolError(msg)
+ else:
+ self.events.ProtocolError(msg.params)
+
####################################################
##
## TestDDA
@@ -888,8 +896,8 @@
##
####################################################
elif msg.name == self.Message.DataFound:
- if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo):
- self.events.ClientGetInfo(msg.params)
+ if msg['Identifier'].startswith(self.IdentifierPrefix.ClientRequestInfo):
+ self.events.ClientRequestInfo(msg.params)
return True
self.events.DataFound(msg.params)
@@ -898,21 +906,21 @@
elif msg.name == self.Message.GetFailed:
code = msg['Code']
if code == self.FetchError.TooBig:
- if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo):
+ if msg['Identifier'].startswith(self.IdentifierPrefix.ClientRequestInfo):
params = {
'Identifier': msg['Identifier'],
'Metadata.ContentType': msg.get('ExpectedMetadata.ContentType', ''),
'DataLength': msg.get('ExpectedDataLength', '')
}
- self.events.ClientGetInfo(params)
+ self.events.ClientRequestInfo(params)
return True
self.events.GetFailed(msg.params)
return True
elif msg.name == self.Message.SimpleProgress:
- if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo):
- self.events.ClientGetInfoProgress(msg.params)
+ if msg['Identifier'].startswith(self.IdentifierPrefix.ClientRequestInfo):
+ self.events.ClientRequestInfoProgress(msg.params)
else:
self.events.SimpleProgress(msg.params)
return True
@@ -1211,7 +1219,7 @@
return identifier
- def clientGetInfo(self, uri, **params):
+ def clientRequestInfo(self, uri, **params):
"""Requests info about a file
@param uri: uri of the file to request info about
@event: clientGetInfo(event, params). If success, params will contain a key 'Metadata.ContentType'
@@ -1220,7 +1228,7 @@
@note: for other events see: L{clientGet}
@return: (str) request identifier
"""
- identifier = self.IdentifierPrefix.ClientGetInfo + self.newIdentifier()
+ identifier = self.IdentifierPrefix.ClientRequestInfo + self.newIdentifier()
self.sendMessage(
self.Message.ClientGet,
Identifier=identifier,
@@ -1394,13 +1402,13 @@
#testListPeerNotes()
- def testClientGetInfo():
+ def testClientRequestInfo():
def cb(event, params):
print params
c.events.ClientGetInfo += cb
- identifier = c.clientGetInfo('CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg')
+ identifier = c.clientRequestInfo('CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg')
for i in xrange(20):
c.next()
- #testClientGetInfo()
+ #testClientRequestInfo()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-07 17:35:29
|
Revision: 57
http://fclient.svn.sourceforge.net/fclient/?rev=57&view=rev
Author: jUrner
Date: 2007-11-07 09:35:34 -0800 (Wed, 07 Nov 2007)
Log Message:
-----------
continued working on downloads widget
Modified Paths:
--------------
trunk/fclient/fclient_widgets/download_widget.py
Modified: trunk/fclient/fclient_widgets/download_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/download_widget.py 2007-11-07 17:35:00 UTC (rev 56)
+++ trunk/fclient/fclient_widgets/download_widget.py 2007-11-07 17:35:34 UTC (rev 57)
@@ -17,15 +17,21 @@
from fclient_lib import fcp
#from fclient_dlgs import frdx_viewer
from fclient_lib.pyex import namespace, numbers
+
+
#from fclient_lib.qt4ex import settingsbase
-#from fclient_lib.qt4ex.ctrls import progressbarwrap
+from fclient_lib.qt4ex.ctrls import progressbarwrap
+from fclient_lib.qt4ex.ctrls import treewidgetwrap
+
+
sys.path.pop(0)
del parentdir
#<-- rel import hack
import thread
+import time
from PyQt4 import QtCore, QtGui
#*****************************************************************************
#
@@ -40,15 +46,31 @@
IndexMimeType = 4
IndexPriority = 5
- ######################
+ #TODO: implement or not ???
IndexLastProgress = 6
IndexDuration = 7
+ StatusNone = 0x0
+ StatusPending = 0x1
+ StatusRequestInfo = 0x2
+ StatusRequestInfoComplete = 0x4
+ StatusDownloading = 0x8
+ StatusDownloadComplete = 0x10
+
+ StatusError = 0x10000
+
+ StatusMaskBusy = StatusRequestInfo | StatusDownloading
+
+
+
+
def __init__(self, parent, requestIdentifier, uri):
QtGui.QTreeWidgetItem.__init__(self, parent)
self._requestIdentifier = requestIdentifier
+ self._requestStatus = self.StatusNone
+ self._requestInitTime = time.time()
self._uri = uri
@@ -58,6 +80,9 @@
def requestIdentifier(self):
return self._requestIdentifier
+ def requestInitTime(self):
+ return self._requestInitTime
+
def setRequestMimeType(self, mimeType):
self.setText(self.IndexMimeType, mimeType)
@@ -82,12 +107,13 @@
def progressWidget(self, tree):
return tree.itemWidget(self, self.IndexProgress)
- def setRequestStatus(self, status):
- self.setText(self.IndexStatus, status)
+ def setRequestStatus(self, status, text=''):
+ self._requestStatus = status
+ self.setText(self.IndexStatus, text)
def requestStatus(self):
- return self.text(self.IndexStatus)
-
+ return self._requestStatus
+
def setRequestSize(self, size):
self.setText(self.IndexSize, size)
@@ -122,6 +148,19 @@
]
self.headerSections.sort()
+ self.itemStatus = {
+ DownloadItem.StatusPending: self.trUtf8('Pending'),
+ DownloadItem.StatusRequestInfo: self.trUtf8('Requesting'),
+ DownloadItem.StatusRequestInfoComplete: self.trUtf8('Found'),
+ DownloadItem.StatusDownloading: self.trUtf8('Loading'),
+ DownloadItem.StatusDownloadComplete: self.trUtf8('Complete'),
+
+ DownloadItem.StatusError: self.trUtf8('Error'),
+ }
+
+
+ self.unknown = self.trUtf8('Unknown')
+
#****************************************************************************
#
@@ -161,24 +200,12 @@
self._lock = thread.allocate_lock()
self._maxQueuedItemsPerHop = maxQueuedItemsPerHop #TODO: move to settings
self._maxSimultaniousDownloads = maxSimultaniousDownloads #TODO: move to settings
- self._strings = DownloadWidgetStrings(self)
-
- # timer to add scheduled downloads (add maxQueuedItemsPerHop / hop to avoid flooding of the tree)
- self._timer = QtCore.QTimer(self)
- self._timer.setInterval(300) # arbitrary delay
- self.connect(
- self._timer,
- QtCore.SIGNAL('timeout()'),
- self.addQueuedItems
- )
-
+ self._strings = None
+
# setup tree
- self.setHeaderLabels([i[1] for i in self._strings.headerSections])
- #self.setColumnCount(len(self._strings.HeaderSections))
self.setSelectionMode(self.ContiguousSelection)
-
header = self.header()
- header.setResizeMode(header.ResizeToContents)
+ #header.setResizeMode(header.ResizeToContents)
header.setStretchLastSection(True)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
@@ -188,7 +215,11 @@
self.handleContextMenu
)
- # schedule initial uris
+
+ self.retranslate()
+
+
+ # schedule initial uris for download
if uris is not None:
self._downloads['DownloadDirectories'][self._directory] = (self, uris)
@@ -217,9 +248,12 @@
"""
"""
self._fcpEvents = (
+ (self._fcpClient.events.Idle, self.handleClientIdle),
(self._fcpClient.events.ClientDisconnected, self.handleClientDisconnected),
(self._fcpClient.events.TestDDAComplete, self.handleTestDDAComplete),
+ (self._fcpClient.events.ClientGetInfo, self.handleClientGetInfo),
+ (self._fcpClient.events.ClientGetInfoProgress, self.handleClientGetInfoProgress),
)
# take care not to connect twice
for event, observer in self._fcpEvents:
@@ -230,7 +264,82 @@
for directory in self._downloads['DownloadDirectories']:
self._fcpClient.testDDA(directory, wantWriteDirectory=True)
+
+
+ def handleClientIdle(self, event, msg):
+
+ # check if there are sownloads queued
+ n = 0
+ while self._downloads['DownloadQueue'] and n < self._maxQueuedItemsPerHop:
+ n += 1
+
+ parent, uri = self._downloads['DownloadQueue'].pop(0)
+ item = DownloadItem(parent, '', uri)
+ fcpUri = self._fcpClient.FcpUri(uri)
+ fileName = fcpUri.fileName()
+ fileName = namespace.unquote_uri(fileName)
+ status = DownloadItem.StatusPending
+
+ item.setRequestName(fileName)
+ item.setRequestStatus(status, self._strings.itemStatus[status])
+
+ #... more here
+
+ # check how many downloads are currently running
+ items = {
+ DownloadItem.StatusPending: [],
+ DownloadItem.StatusRequestInfoComplete: [],
+ }
+ itemsBusy = 0
+ enum = treewidgetwrap.TreeWidgetIterator(self)
+ for item in enum:
+ status = item.requestStatus()
+ if status & item.StatusMaskBusy:
+ itemsBusy += 1
+ elif status & item.StatusPending:
+ items[status].append(item)
+ elif status & item.StatusRequestInfoComplete:
+ items[status].append(item)
+
+ # start items with info requested
+ itemsToBeStarted = self._maxSimultaniousDownloads - itemsBusy
+ if itemsToBeStarted > 0:
+ def sortf(item1, item2):
+ return cmp(item1.requestInitTime(), item2.requestInitTime())
+
+ infoCompleteItems = items[DownloadItem.StatusRequestInfoComplete]
+ infoCompleteItems.sort(cmp=sortf)
+ while infoCompleteItems:
+
+ #TODO: not yet implemented
+ break
+ itemsToBeStarted -= 1
+
+
+ # start pending items
+ if itemsToBeStarted > 0:
+ pendingItems = items[DownloadItem.StatusPending]
+ pendingItems.sort(cmp=sortf)
+ while pendingItems:
+ itemsToBeStarted -= 1
+ if itemsToBeStarted < 0:
+ break
+
+ item = pendingItems.pop(0)
+ progress = progressbarwrap.ProgressBarEx(self)
+ uri = item.requestUri()
+
+
+ progress.setRange(0, 0)
+ progress.setValue(0)
+ item.setProgressWidget(self, progress)
+
+ identifier = self._fcpClient.clientGetInfo(uri)
+ self._downloads['Downloads'][identifier] = item
+
+
+
def handleClientDisconnected(self, event, params):
"""
"""
@@ -243,14 +352,9 @@
writeAllowed = params.get('WriteDirectoryAllowed')
# check if there are items to be donloaded for the directory
- downloads = None
- self._lock.acquire(True)
- try:
- downloads = self._downloads['DownloadDirectories'].get(directory, None)
- if downloads is not None:
- del self._downloads['DownloadDirectories'][directory] # ???
- finally:
- self._lock.release()
+ downloads = self._downloads['DownloadDirectories'].get(directory, None)
+ if downloads is not None:
+ del self._downloads['DownloadDirectories'][directory] # ???
if downloads:
if writeAllowed == self._fcpClient.FcpTrue:
@@ -261,52 +365,60 @@
else:
pass
#TODO: write access denied, error
-
+
+
+ def handleClientGetInfo(self, event, params):
+
+ identifier = params['Identifier']
+ item = self._downloads['Downloads'].get(identifier, None)
+ if item is not None:
+ del self._downloads['Downloads'][identifier]
+
+ status = DownloadItem.StatusRequestInfoComplete
+ mimeType = params.get('Metadata.ContentType', self._strings.unknown)
+ dataLength = params.get('DataLength', '')
+ try:
+ dataLength = int(dataLength)
+ except ValueError:
+ dataLength = self._strings.unknown
+ else:
+ dataLength = numbers.format_num_bytes(dataLength)
+
+ item.setRequestStatus(status, self._strings.itemStatus[status])
+ item.setRequestMimeType(mimeType)
+ item.setRequestSize(dataLength)
+
+
+
+ def handleClientGetInfoProgress(self, event, params):
+
+ identifier = params['Identifier']
+ item = self._downloads['Downloads'].get(identifier, None)
+ if item is not None:
+ progress = item.progressWidget(self)
+ required=int(params['Required'])
+ succeeded=int(params['Succeeded'])
+
+ if required != progress.maximum():
+ progress.setRange(0, required)
+ progress.setValue(succeeded)
+
#######################################################
##
## methods
##
#######################################################
- def addQueuedItems(self):
-
- self._timer.stop()
-
- self._lock.acquire(True)
- try:
- n = 0
- while self._downloads['DownloadQueue'] and n < self._maxQueuedItemsPerHop:
- n += 1
-
- parent, uri = self._downloads['DownloadQueue'].pop(0)
- item = DownloadItem(parent, '', uri)
- fcpUri = self._fcpClient.FcpUri(uri)
- fileName = fcpUri.fileName()
- fileName = namespace.unquote_uri(fileName)
-
- item.setRequestName(fileName)
-
- #... more here
-
- if self._downloads['DownloadQueue']:
- self._timer.start()
-
- finally:
- self._lock.release()
-
-
def download(self, uri, parent=None):
- self._lock.acquire(True)
- try:
- self._downloads['DownloadQueue'].append( (parent, uri) )
- finally:
- self._lock.release()
+ self._downloads['DownloadQueue'].append( (parent, uri) )
+ #TODO: adjust priority?
- if not self._timer.isActive():
- self._timer.start()
-
-
+
def retranslate(self):
- pass
+ self._strings = DownloadWidgetStrings(self)
+ self.setHeaderLabels([i[1] for i in self._strings.headerSections])
+
+ #TODO: retranslate item status
+
#***************************************************************************************************
#
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-07 17:35:03
|
Revision: 56
http://fclient.svn.sourceforge.net/fclient/?rev=56&view=rev
Author: jUrner
Date: 2007-11-07 09:35:00 -0800 (Wed, 07 Nov 2007)
Log Message:
-----------
config was broken - fixed
Modified Paths:
--------------
trunk/fclient/fclient_widgets/peer_widget.py
Modified: trunk/fclient/fclient_widgets/peer_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/peer_widget.py 2007-11-07 17:34:24 UTC (rev 55)
+++ trunk/fclient/fclient_widgets/peer_widget.py 2007-11-07 17:35:00 UTC (rev 56)
@@ -67,13 +67,15 @@
def __init__(self,
parent,
- cfg=None
+ cfg=None,
connectionName='',
):
"""
"""
+ QtGui.QWidget.__init__(self, parent)
+
self._connectionName = connectionName
- self._cfg = cfg if cfg is None else config.Config(self)
+ self._cfg = cfg if cfg is not None else config.Config(self)
self._fcpClient = None
self._fcpEvents = None
self._isCreated = False
@@ -81,8 +83,7 @@
self._requestsPending = 0
self._showPeersFlags = self.ShowPeersAll
- QtGui.QWidget.__init__(self, parent)
-
+
# setup tree
self.setUniformRowHeights(True)
headerLabels = [
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-07 17:34:22
|
Revision: 55
http://fclient.svn.sourceforge.net/fclient/?rev=55&view=rev
Author: jUrner
Date: 2007-11-07 09:34:24 -0800 (Wed, 07 Nov 2007)
Log Message:
-----------
poll timer is single shot now to avoid troubles with reentrant timer
Modified Paths:
--------------
trunk/fclient/fclient_ui/fcp_client_manager.py
Modified: trunk/fclient/fclient_ui/fcp_client_manager.py
===================================================================
--- trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-07 17:33:26 UTC (rev 54)
+++ trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-07 17:34:24 UTC (rev 55)
@@ -242,10 +242,11 @@
)
self.pollTimer = QtCore.QTimer()
+ self.pollTimer.setSingleShot(True)
QtCore.QObject.connect(
self.pollTimer,
QtCore.SIGNAL('timeout()'),
- self.fcpClient.next
+ self.handleFcpClientNext
)
@@ -306,7 +307,14 @@
except StopIteration:
return
self.connectTimer.start(int(self.connectPriority))
+
+ def handleFcpClientNext(self):
+ """Handles reading next message from FcpClient"""
+ self.fcpClient.next()
+ self.pollTimer.start(int(self.pollPriority))
+
+
#******************************************************************************************
#
#******************************************************************************************
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-07 17:33:38
|
Revision: 54
http://fclient.svn.sourceforge.net/fclient/?rev=54&view=rev
Author: jUrner
Date: 2007-11-07 09:33:26 -0800 (Wed, 07 Nov 2007)
Log Message:
-----------
added an idle event and fixed a missing identifier in EventClientGetInfo
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-07 17:32:18 UTC (rev 53)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-07 17:33:26 UTC (rev 54)
@@ -141,6 +141,8 @@
"""All events the client supports"""
_events_ = (
+ 'Idle',
+
'ClientConnected',
'ClientDisconnected',
@@ -898,6 +900,7 @@
if code == self.FetchError.TooBig:
if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo):
params = {
+ 'Identifier': msg['Identifier'],
'Metadata.ContentType': msg.get('ExpectedMetadata.ContentType', ''),
'DataLength': msg.get('ExpectedDataLength', '')
}
@@ -984,7 +987,12 @@
}
self.events.ClientDisconnected(params)
raise self.SocketError(msg['Details'])
- self.handleMessage(msg)
+
+ elif msg.name == self.Message.ClientSocketTimeout:
+ self.events.Idle(msg)
+
+ else:
+ self.handleMessage(msg)
return msg
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-07 17:33:38
|
Revision: 53
http://fclient.svn.sourceforge.net/fclient/?rev=53&view=rev
Author: jUrner
Date: 2007-11-07 09:32:18 -0800 (Wed, 07 Nov 2007)
Log Message:
-----------
added a todo
Modified Paths:
--------------
trunk/fclient/config.py
Modified: trunk/fclient/config.py
===================================================================
--- trunk/fclient/config.py 2007-11-06 12:40:00 UTC (rev 52)
+++ trunk/fclient/config.py 2007-11-07 17:32:18 UTC (rev 53)
@@ -30,7 +30,6 @@
"DlgPreferences": _(DocDir, 'dialogs', 'preferences.html'),
}
-
#*****************************************************************************
#
#*****************************************************************************
@@ -57,6 +56,7 @@
#TODO: assistant can not handle locale dependend docs yet
self.assistant = assistant.Assistant(parent, profile=AssistantProfile, pages=DocPages)
+ #TODO: no good idea to have a default directory. May lead to troubles when making fclient executable
self.defaultDownloadDir = os.path.join(BaseDir, 'download')
self.fcpClientManager = FcpClientManager(parent)
self.language = language.Language([LanguageDir, ], translationPrefixes=TranslationPrefixes)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-06 12:40:00
|
Revision: 52
http://fclient.svn.sourceforge.net/fclient/?rev=52&view=rev
Author: jUrner
Date: 2007-11-06 04:40:00 -0800 (Tue, 06 Nov 2007)
Log Message:
-----------
added a few methods
Modified Paths:
--------------
trunk/fclient/fclient_ui/fcp_client_manager.py
Modified: trunk/fclient/fclient_ui/fcp_client_manager.py
===================================================================
--- trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-06 12:39:13 UTC (rev 51)
+++ trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-06 12:40:00 UTC (rev 52)
@@ -109,15 +109,16 @@
self.priorities = Priorities(self)
- def stopClient(self, name): #TODO: name of client or client???
- """Stops a FcpClient
- @name: (str) connection name of the client to stop
- @note: the client is not closed in the call. To close the connection call FcpClient.close()
+ def closeClient(self, name):
+ """Closes a FcpClient
+ @name: (str) connection name of the client to close
+ @note: the client is unregisatered and closed in the call.
"""
handler = self.fcpClients.pop(name)
handler.stop()
+ handler.fcpClient.close()
-
+
def newClient(self,
name,
eventConnectedHandler,
@@ -168,8 +169,24 @@
handler.setPollPriority(pollPriority)
+ def startClient(self, name):
+ """Starts a FcpClient
+ @name: (str) connection name of the client to start
+ """
+ handler = self.fcpClients[name]
+ handler.start()
+
+
+ def stopClient(self, name):
+ """Stops a FcpClient
+ @name: (str) connection name of the client to stop
+ """
+ handler = self.fcpClients[name]
+ handler.stop()
+
+
def handleFcpClientDisconnected(self, handler, params):
- """Handles FcpClient.EventDisconnect"""
+ """Global handler for all clients to handle FcpClient.EventDisconnect"""
disconnectReason = params['DisconnectReason']
fcpClient = handler.fcpClient
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-06 12:39:15
|
Revision: 51
http://fclient.svn.sourceforge.net/fclient/?rev=51&view=rev
Author: jUrner
Date: 2007-11-06 04:39:13 -0800 (Tue, 06 Nov 2007)
Log Message:
-----------
started implementing a download widget
Added Paths:
-----------
trunk/fclient/fclient_widgets/download_widget.py
Added: trunk/fclient/fclient_widgets/download_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/download_widget.py (rev 0)
+++ trunk/fclient/fclient_widgets/download_widget.py 2007-11-06 12:39:13 UTC (rev 51)
@@ -0,0 +1,358 @@
+"""Sketch for a download widget
+
+"""
+
+
+import os, sys
+
+#--> rel import hack
+def parentdir(n, fpath):
+ fpath = os.path.abspath(fpath)
+ for i in xrange(n):
+ fpath = os.path.dirname(fpath)
+ return fpath
+sys.path.insert(0, parentdir(2, __file__))
+
+import config
+from fclient_lib import fcp
+#from fclient_dlgs import frdx_viewer
+from fclient_lib.pyex import namespace, numbers
+#from fclient_lib.qt4ex import settingsbase
+#from fclient_lib.qt4ex.ctrls import progressbarwrap
+
+sys.path.pop(0)
+del parentdir
+#<-- rel import hack
+
+
+import thread
+from PyQt4 import QtCore, QtGui
+#*****************************************************************************
+#
+#*****************************************************************************
+class DownloadItem(QtGui.QTreeWidgetItem):
+
+ # default indices
+ IndexName = 0
+ IndexStatus = 1
+ IndexProgress = 2
+ IndexSize = 3
+ IndexMimeType = 4
+ IndexPriority = 5
+
+ ######################
+ IndexLastProgress = 6
+ IndexDuration = 7
+
+
+ def __init__(self, parent, requestIdentifier, uri):
+ QtGui.QTreeWidgetItem.__init__(self, parent)
+
+ self._requestIdentifier = requestIdentifier
+ self._uri = uri
+
+
+ def setRequestIdentifier(self, identifier):
+ self._requestIdentifier = identifier
+
+ def requestIdentifier(self):
+ return self._requestIdentifier
+
+ def setRequestMimeType(self, mimeType):
+ self.setText(self.IndexMimeType, mimeType)
+
+ def requestMimeType(self):
+ return self.text(self.IndexMimeType)
+
+ def setRequestName(self, name):
+ self.setText(self.IndexName, name)
+
+ def requestName(self):
+ return self.text(self.IndexName)
+
+ def setRequestPriority(self, priority):
+ self.setText(self.IndexPriority, priority)
+
+ def requestPriority(self):
+ return self.text(self.IndexPriority)
+
+ def setProgressWidget(self, tree, widget):
+ tree.setItemWidget(self, self.IndexProgress, widget)
+
+ def progressWidget(self, tree):
+ return tree.itemWidget(self, self.IndexProgress)
+
+ def setRequestStatus(self, status):
+ self.setText(self.IndexStatus, status)
+
+ def requestStatus(self):
+ return self.text(self.IndexStatus)
+
+ def setRequestSize(self, size):
+ self.setText(self.IndexSize, size)
+
+ def requestSize(self):
+ return self.text(self.IndexSize)
+
+ def setRequestUri(self, uri):
+ self._uri = uri
+
+ def requestUri(self):
+ return self._uri
+
+#*****************************************************************************
+#
+#*****************************************************************************
+class DownloadWidgetStrings(QtCore.QObject):
+
+
+ def __init__(self, parent):
+ QtCore.QObject.__init__(self, parent)
+
+
+ self.headerSections = [
+ (DownloadItem.IndexName, self.trUtf8('Name')),
+ (DownloadItem.IndexStatus, self.trUtf8('Status')),
+ (DownloadItem.IndexProgress, self.trUtf8('Progress')),
+ (DownloadItem.IndexSize, self.trUtf8('Size')),
+ (DownloadItem.IndexMimeType, self.trUtf8('Content')),
+ (DownloadItem.IndexPriority, self.trUtf8('Priority')),
+ (DownloadItem.IndexLastProgress, self.trUtf8('Last Progress')),
+ (DownloadItem.IndexDuration, self.trUtf8('Duration')),
+ ]
+ self.headerSections.sort()
+
+
+#****************************************************************************
+#
+#****************************************************************************
+class DownloadWidget(QtGui.QTreeWidget):
+
+ def __init__(self,
+ parent,
+ connectionName='',
+ directory=None,
+ uris=None,
+ maxQueuedItemsPerHop=200,
+ maxSimultaniousDownloads=2,
+ cfg=None
+ ):
+ """
+ @param parent: (QWidget) parent or None
+ @param directory: (str) directory to sownload items to or None to use default directory
+ @param connectionName: name of the connection to the node
+ @param maxQueuedItemsPerHop: maximum number of items that can be e added in one hop
+ @param maxSimultaniousDownloads: maximim number of simultaneous downloads
+ @param cfg: (configConfig) instance or None
+ """
+
+ QtGui.QTreeWidget.__init__(self, parent)
+
+ self._connectionName = connectionName
+ self._cfg = cfg if cfg is not None else config.Config(self)
+ self._directory = directory if directory is not None else self._cfg.defaultDownloadDir
+ self._downloads = {
+ 'Downloads': {}, # identifier --> item / items currently downloading
+ 'DownloadQueue': [], # (parent-item, uri) / items scheduled for download
+ 'DownloadDirectories': {}, # directory --> (parentItem, uris) / directories scheduled for testDDA
+ }
+ self._fcpClient = None
+ self._isCreated = False
+ self._lock = thread.allocate_lock()
+ self._maxQueuedItemsPerHop = maxQueuedItemsPerHop #TODO: move to settings
+ self._maxSimultaniousDownloads = maxSimultaniousDownloads #TODO: move to settings
+ self._strings = DownloadWidgetStrings(self)
+
+ # timer to add scheduled downloads (add maxQueuedItemsPerHop / hop to avoid flooding of the tree)
+ self._timer = QtCore.QTimer(self)
+ self._timer.setInterval(300) # arbitrary delay
+ self.connect(
+ self._timer,
+ QtCore.SIGNAL('timeout()'),
+ self.addQueuedItems
+ )
+
+ # setup tree
+ self.setHeaderLabels([i[1] for i in self._strings.headerSections])
+ #self.setColumnCount(len(self._strings.HeaderSections))
+ self.setSelectionMode(self.ContiguousSelection)
+
+ header = self.header()
+ header.setResizeMode(header.ResizeToContents)
+ header.setStretchLastSection(True)
+
+ self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.connect(
+ self,
+ QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'),
+ self.handleContextMenu
+ )
+
+ # schedule initial uris
+ if uris is not None:
+ self._downloads['DownloadDirectories'][self._directory] = (self, uris)
+
+
+ def showEvent(self, event):
+ if not self._isCreated:
+ self._isCreated = True
+ self._fcpClient = self._cfg.fcpClientManager.newClient(self._connectionName, self.handleClientConnected)
+
+ #############################################################
+ ##
+ ## handlers for Qt events
+ ##
+ #############################################################
+ def handleContextMenu(self):
+ pass
+
+
+
+ #############################################################
+ ##
+ ## handlers for Fcp events
+ ##
+ #############################################################
+ def handleClientConnected(self, event, params):
+ """
+ """
+ self._fcpEvents = (
+ (self._fcpClient.events.ClientDisconnected, self.handleClientDisconnected),
+
+ (self._fcpClient.events.TestDDAComplete, self.handleTestDDAComplete),
+ )
+ # take care not to connect twice
+ for event, observer in self._fcpEvents:
+ if not observer in event:
+ event += observer
+
+ # register directories
+ for directory in self._downloads['DownloadDirectories']:
+ self._fcpClient.testDDA(directory, wantWriteDirectory=True)
+
+
+ def handleClientDisconnected(self, event, params):
+ """
+ """
+
+ def handleTestDDAComplete(self, event, params):
+ """
+ """
+ directory = params['Directory']
+ readAllowed = params.get('WriteDirectoryAllowed')
+ writeAllowed = params.get('WriteDirectoryAllowed')
+
+ # check if there are items to be donloaded for the directory
+ downloads = None
+ self._lock.acquire(True)
+ try:
+ downloads = self._downloads['DownloadDirectories'].get(directory, None)
+ if downloads is not None:
+ del self._downloads['DownloadDirectories'][directory] # ???
+ finally:
+ self._lock.release()
+
+ if downloads:
+ if writeAllowed == self._fcpClient.FcpTrue:
+ parent, uris = downloads
+ for uri in uris:
+ self.download(uri, parent=parent)
+
+ else:
+ pass
+ #TODO: write access denied, error
+
+ #######################################################
+ ##
+ ## methods
+ ##
+ #######################################################
+ def addQueuedItems(self):
+
+ self._timer.stop()
+
+ self._lock.acquire(True)
+ try:
+ n = 0
+ while self._downloads['DownloadQueue'] and n < self._maxQueuedItemsPerHop:
+ n += 1
+
+ parent, uri = self._downloads['DownloadQueue'].pop(0)
+ item = DownloadItem(parent, '', uri)
+ fcpUri = self._fcpClient.FcpUri(uri)
+ fileName = fcpUri.fileName()
+ fileName = namespace.unquote_uri(fileName)
+
+ item.setRequestName(fileName)
+
+ #... more here
+
+ if self._downloads['DownloadQueue']:
+ self._timer.start()
+
+ finally:
+ self._lock.release()
+
+
+ def download(self, uri, parent=None):
+ self._lock.acquire(True)
+ try:
+ self._downloads['DownloadQueue'].append( (parent, uri) )
+ finally:
+ self._lock.release()
+
+ if not self._timer.isActive():
+ self._timer.start()
+
+
+ def retranslate(self):
+ pass
+
+#***************************************************************************************************
+#
+#***************************************************************************************************
+if __name__ == '__main__':
+ import sys
+
+ TestUris = '''CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg
+CHK@q4~2soHTd9SOINIoXmg~dn7LNUAOYzN1tHNHT3j4c9E,gcVRtoglEhgqN-DJolXPqJ4yX1f~1gBGh89HNWlFMWQ,AAIC--8/snow_002%20%2810%29.jpg
+CHK@6tw-vrINDoNgSY9pTq-88n2sOAhREv1iFc3T0E18xHk,NxJxo9d7spLHCqGsMyY26XtXpUACPKTJq9v~WxVp3ic,AAIC--8/snow_002%20%2811%29.jpg
+CHK@nOb-ZM4h1SNpoG3E0xcOPnNDydSf~y2DON~R80exAtU,2o6smJYijaftLzMz4iva24T1geEAnDFn~bIMKFkzU30,AAIC--8/snow_002%20%2812%29.jpg
+CHK@XWyYvxoirEKiLWhnSVdKffSHT~nnKWsVZ5tJRKiFxBk,mK9-g5AeCvcNue1wYHRhc1Hdj3-yn77Vyu9Tq7XXJTM,AAIC--8/snow_002%20%2813%29.jpg
+CHK@AbQOZRSJcgXnKOI16D2hF8qkjxp3cl1u72bkL2KS29E,jjQlvojCkJJgzI2WgWj5rMsmUd935zjGChiV-j937X0,AAIC--8/snow_002%20%2814%29.jpg
+CHK@1ZW0fOpB7RmUFqSO2TtAOyfFm1uJ6~JcWuPESiYwtc4,aCDkI~JCr3Mb8s7rPBgmalPqzQEFdovlF~DgDRkGWuE,AAIC--8/snow_002%20%2815%29.jpg
+CHK@a8jHCl0TkwVAcTdYOezd-FtIWTLIFhjrWeHXVcSjRPk,4rIwDZE~-Gp~B5tjDSAcSpn~tLgMmnMUFeikXP4f7y0,AAIC--8/snow_002%20%2816%29.jpg
+CHK@Jg~F~~WbKu8AlBgmwkonWGQNzZFUholE2CkGnyt23kk,Gj7JtnR97EIRvabG8m6Xp1dUAqiP6qD4J5FJRN9waMQ,AAIC--8/snow_002%20%2836%29.jpg'''
+ TestUris = TestUris.split('\n')
+
+
+
+ app = QtGui.QApplication(sys.argv)
+ w = QtGui.QMainWindow()
+ peers = DownloadWidget(None,
+ connectionName='TestDownloadWidget',
+ uris=TestUris
+
+ )
+ w.setCentralWidget(peers)
+
+ #m = w.menuBar()
+ #m1 = m.addMenu('Peers')
+ #peers.populateMenu(m1)
+
+ w.show()
+ res = app.exec_()
+ sys.exit(res)
+
+
+
+
+
+
+
+
+
+
+
+
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-06 12:38:46
|
Revision: 50
http://fclient.svn.sourceforge.net/fclient/?rev=50&view=rev
Author: jUrner
Date: 2007-11-06 04:38:50 -0800 (Tue, 06 Nov 2007)
Log Message:
-----------
added connectionName param
Modified Paths:
--------------
trunk/fclient/fclient_widgets/peer_widget.py
Modified: trunk/fclient/fclient_widgets/peer_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/peer_widget.py 2007-11-06 12:35:18 UTC (rev 49)
+++ trunk/fclient/fclient_widgets/peer_widget.py 2007-11-06 12:38:50 UTC (rev 50)
@@ -34,7 +34,6 @@
"""
"""
QtGui.QTreeWidgetItem.__init__(self, *args)
-
self._peerData = None
def peerData(self):
@@ -66,10 +65,15 @@
ShowPeersAll = ShowPeersOpennet | ShowPeersDarknet
- def __init__(self, parent, cfg=None):
+ def __init__(self,
+ parent,
+ cfg=None
+ connectionName='',
+ ):
"""
"""
- self._cfg = cfg
+ self._connectionName = connectionName
+ self._cfg = cfg if cfg is None else config.Config(self)
self._fcpClient = None
self._fcpEvents = None
self._isCreated = False
@@ -79,10 +83,6 @@
QtGui.QWidget.__init__(self, parent)
- # setup config
- if self._cfg is None:
- self._cfg = config.Config(self)
-
# setup tree
self.setUniformRowHeights(True)
headerLabels = [
@@ -112,7 +112,7 @@
"""
if not self._isCreated:
self._isCreated = True
- self._fcpClient = self._cfg.fcpClientManager.newClient('MeName', self.handleClientConnected)
+ self._fcpClient = self._cfg.fcpClientManager.newClient(self._connectionName, self.handleClientConnected)
#############################################################
##
@@ -147,7 +147,7 @@
(self._fcpClient.events.EndListPeerNotes, self.handleEndListPeerNotes),
)
- # take care to not connect twice
+ # take care not to connect twice
for event, observer in self._fcpEvents:
if not observer in event:
event += observer
@@ -155,9 +155,7 @@
##
self.listPeers()
-
-
-
+
def handleClientDisconnected(self, event, params):
"""
"""
@@ -210,8 +208,7 @@
pollPriority=self._cfg.fcpClientManager.priorities['Lowest']
)
-
-
+
def handlePeerRemoved(self, peer):
"""
"""
@@ -258,7 +255,7 @@
"""
# disconnect Fcp events
for event, observer in self._fcpEvents:
- if not observer in event:
+ if observer in event:
event -= observer
self._cfg.fcpClientHandler.closeClient(self._fcpClient.connectionName())
@@ -528,7 +525,7 @@
app = QtGui.QApplication(sys.argv)
w = QtGui.QMainWindow()
- peers = PeerWidget(None)
+ peers = PeerWidget(None, connectionName='TestPeerWidget')
w.setCentralWidget(peers)
m = w.menuBar()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-06 12:35:15
|
Revision: 49
http://fclient.svn.sourceforge.net/fclient/?rev=49&view=rev
Author: jUrner
Date: 2007-11-06 04:35:18 -0800 (Tue, 06 Nov 2007)
Log Message:
-----------
minor changes
Modified Paths:
--------------
trunk/fclient/config.py
Modified: trunk/fclient/config.py
===================================================================
--- trunk/fclient/config.py 2007-11-06 12:34:50 UTC (rev 48)
+++ trunk/fclient/config.py 2007-11-06 12:35:18 UTC (rev 49)
@@ -57,7 +57,7 @@
#TODO: assistant can not handle locale dependend docs yet
self.assistant = assistant.Assistant(parent, profile=AssistantProfile, pages=DocPages)
- self.defaultDownloadsDir = os.path.join(BaseDir, 'downloads')
+ self.defaultDownloadDir = os.path.join(BaseDir, 'download')
self.fcpClientManager = FcpClientManager(parent)
self.language = language.Language([LanguageDir, ], translationPrefixes=TranslationPrefixes)
self.resources = resources.Resources([ResourceDir, ], )
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-06 12:34:48
|
Revision: 48
http://fclient.svn.sourceforge.net/fclient/?rev=48&view=rev
Author: jUrner
Date: 2007-11-06 04:34:50 -0800 (Tue, 06 Nov 2007)
Log Message:
-----------
minor changes
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-05 12:50:42 UTC (rev 47)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-06 12:34:50 UTC (rev 48)
@@ -222,6 +222,7 @@
NotAllDataFound = '28'
class FcpError(Exception): pass
+ #TODO: better description of keys (MaxKeyLen ...) so we can use regex(es) to parse arbitrary strings for keys
class FcpUri(object):
"""Wrapper class for freenet uris"""
@@ -362,7 +363,7 @@
KeyboardInterrupt = 'Keyboard interrupt'
SocketDied = 'Socket died'
- #TODO: maybe speed up lookup of message name lookup by implementing integer message names
+ #TODO: maybe speed up message name lookup by implementing integer message names
class Message(object):
"""Class wrapping a freenet message"""
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-05 12:50:37
|
Revision: 47
http://fclient.svn.sourceforge.net/fclient/?rev=47&view=rev
Author: jUrner
Date: 2007-11-05 04:50:42 -0800 (Mon, 05 Nov 2007)
Log Message:
-----------
svn fix
Added Paths:
-----------
trunk/fclient/fclient_lib/qt4ex/
trunk/fclient/fclient_lib/qt4ex/LICENCE.MIT
trunk/fclient/fclient_lib/qt4ex/README
trunk/fclient/fclient_lib/qt4ex/__init__.py
trunk/fclient/fclient_lib/qt4ex/assistant.py
trunk/fclient/fclient_lib/qt4ex/ctrls/
trunk/fclient/fclient_lib/qt4ex/ctrls/__init__.py
trunk/fclient/fclient_lib/qt4ex/ctrls/areatips.py
trunk/fclient/fclient_lib/qt4ex/ctrls/checkarraywrap.py
trunk/fclient/fclient_lib/qt4ex/ctrls/colorbutton.py
trunk/fclient/fclient_lib/qt4ex/ctrls/compactpatwrap.py
trunk/fclient/fclient_lib/qt4ex/ctrls/dragtool.py
trunk/fclient/fclient_lib/qt4ex/ctrls/editboxwrap.py
trunk/fclient/fclient_lib/qt4ex/ctrls/labelwrap.py
trunk/fclient/fclient_lib/qt4ex/ctrls/mrumenu.py
trunk/fclient/fclient_lib/qt4ex/ctrls/progressbarwrap.py
trunk/fclient/fclient_lib/qt4ex/ctrls/tablewidget.py
trunk/fclient/fclient_lib/qt4ex/ctrls/toolbarwrap.py
trunk/fclient/fclient_lib/qt4ex/ctrls/treewidgetwrap.py
trunk/fclient/fclient_lib/qt4ex/dlgs/
trunk/fclient/fclient_lib/qt4ex/dlgs/__init__.py
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgabout/
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgabout/DlgAbout.ui
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgabout/Ui_DlgAbout.py
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgabout/__init__.py
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgfindreplace/
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgfindreplace/DlgFindReplace.ui
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgfindreplace/Ui_DlgFindReplace.py
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgfindreplace/__init__.py
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgpreferences/
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgpreferences/DlgPreferencesTree.ui
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgpreferences/Ui_DlgPreferencesTree.py
trunk/fclient/fclient_lib/qt4ex/dlgs/dlgpreferences/__init__.py
trunk/fclient/fclient_lib/qt4ex/lang/
trunk/fclient/fclient_lib/qt4ex/lang/qt4ex_de.ts
trunk/fclient/fclient_lib/qt4ex/lang/qt4ex_en.ts
trunk/fclient/fclient_lib/qt4ex/language.py
trunk/fclient/fclient_lib/qt4ex/qt4ex.pro
trunk/fclient/fclient_lib/qt4ex/qtools.py
trunk/fclient/fclient_lib/qt4ex/res/
trunk/fclient/fclient_lib/qt4ex/res/language/
trunk/fclient/fclient_lib/qt4ex/res/language/LangCodes-ISO 639-1.txt
trunk/fclient/fclient_lib/qt4ex/resources.py
trunk/fclient/fclient_lib/qt4ex/scripts/
trunk/fclient/fclient_lib/qt4ex/scripts/__init__.py
trunk/fclient/fclient_lib/qt4ex/scripts/manifest.py
trunk/fclient/fclient_lib/qt4ex/scripts/pylupdate.py
trunk/fclient/fclient_lib/qt4ex/scripts/qtpro.py
trunk/fclient/fclient_lib/qt4ex/settingsbase.py
Added: trunk/fclient/fclient_lib/qt4ex/LICENCE.MIT
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/LICENCE.MIT (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/LICENCE.MIT 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1,21 @@
+
+qt4ex -- Qt5 / PyQt4 extensions
+
+Copyright (c) 2007 J\xFCrgen Urner
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+and associated documentation files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
+OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
Added: trunk/fclient/fclient_lib/qt4ex/README
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/README (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/README 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1,23 @@
+ qt4ex -- Qt5 / PyQt4 extensions
+
+
+
+Version history:
+
+*******************************************************************
+0.1.0
+*******************************************************************
+news:
+ x. initial release
+
+bugfixes:
+ x.
+
+
+
+
+
+
+
+
+
Added: trunk/fclient/fclient_lib/qt4ex/__init__.py
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/__init__.py (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/__init__.py 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1,11 @@
+"""Qt4 extensions
+
+"""
+
+__version__ = '0.1.0'
+__author__ = 'Juergen Urner'
+__emeil__ = 'jU...@ar...'
+__licence__ = 'Mit'
+__copyright__ = '(c) 2007 Juergen Urner'
+
+
Added: trunk/fclient/fclient_lib/qt4ex/assistant.py
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/assistant.py (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/assistant.py 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1,130 @@
+
+import os
+from PyQt4 import QtCore, QtGui
+
+__version__ = '0.0.1'
+#*****************************************************************
+#
+#*****************************************************************
+class Assistant(QtCore.QObject):
+ """Class to tame QAssistant a bit
+
+ Usage:
+
+ * Feed a profile and a dict of pages to the class
+ * call showPage() with the widget and the name of a page to show as params
+ * call close() with the widget as param the page is shown for when the widget is closed
+
+ An internal refcount is kept to enshure QAssistant is not closed any sooner
+ than as when the last widget refering to it is closed. And as a bonus closing
+ a page for a widget will switch back to the page opend by the previous widget
+
+ @note: currently only one open page / widget is supported
+ """
+ def __init__(self, parent, profile, pages):
+ """
+ @param profile: (absolute) path of the profile file to dfeed to assistant
+ @param pages: a dict{name-of-page: fpath-page} of pages
+ """
+ QtCore.QObject.__init__(self, parent)
+
+ self._referers = [[], []] # [widgets, page-names]
+ self._isInited = False
+
+ self.qtAssistant = None
+ self.pages = pages
+ self.parent = parent
+ self.profile = profile
+
+ if self.pages is None:
+ self.pages = {}
+
+
+ ################################################
+ ## slots
+ ################################################
+ def onAssistantClosed(self):
+ self._referers = [[], []]
+
+ ################################################
+ ## methods
+ ################################################
+ def close(self, parent):
+ """Closes assistant if open and no other widget currently has a page open
+ """
+ if self.qtAssistant is not None:
+ widgets, pages = self._referers
+ if parent in widgets:
+ i = widgets.index(parent)
+ del widgets[i]
+ del pages[i]
+ if widgets:
+ self.showPage(widgets[-1], pages[-1], register=False)
+ else:
+ if self.qtAssistant.isOpen():
+ self.qtAssistant.closeAssistant()
+ return True
+ return False
+
+
+ def error(self, msg):
+ QtGui.QMessageBox.critical(self.parent, self.trUtf8('Qt assistant error'), msg)
+ return False
+
+
+ def initAssistant(self, parent):
+ """Inits QAssistant client if not already done"""
+ if self._isInited:
+ return
+ else:
+ self._isInited = True
+
+ try:
+ from PyQt4 import QtAssistant
+ except ImportError:
+ pass
+ else:
+ self.qtAssistant = QtAssistant.QAssistantClient('', parent)
+ self.qtAssistant.connect(
+ self.qtAssistant,
+ QtCore.SIGNAL('error(const QSting&)'),
+ self.error
+ )
+ self.qtAssistant.connect(
+ self.qtAssistant,
+ QtCore.SIGNAL('assistantClosed()'),
+ self.onAssistantClosed
+ )
+
+ L = QtCore.QStringList('-profile' ) << self.profile
+ self.qtAssistant.setArguments(L)
+
+
+ def showPage(self, parent, name, register=True):
+ """Shows a page
+ @param widget: widget to show the page for
+ @param name: name of the page
+ @param register: if True the widget will be registerd in the stack of open pages
+ """
+ self.initAssistant(parent)
+ if self.qtAssistant is None:
+ return self.error(self.trUtf8('Can not display help. Assistant is not installed'))
+
+ fpath = self.pages.get(name, None)
+ if fpath is None:
+ return self.error(self.trUtf8('Can not display help. No such page: ') + name)
+ if not os.path.isfile(fpath):
+ return self.error(self.trUtf8('Can not display help. Page not found: ') + '\n' + fpath)
+ if register and parent not in self._referers[0]:
+ self._referers[0].append(parent)
+ self._referers[1].append(name)
+ self.qtAssistant.showPage(fpath)
+ return True
+
+
+
+
+
+
+
+
\ No newline at end of file
Added: trunk/fclient/fclient_lib/qt4ex/ctrls/__init__.py
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/ctrls/__init__.py (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/ctrls/__init__.py 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1 @@
+
Added: trunk/fclient/fclient_lib/qt4ex/ctrls/areatips.py
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/ctrls/areatips.py (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/ctrls/areatips.py 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1,465 @@
+# -*- coding: utf-8 -*-
+
+"""Customized tooltips
+"""
+
+# TODO:
+#
+# x. fix ComboBoxEditTip to act as promised
+# x. add classes to handle temTips for editBoxes (...)
+#
+# x. ComboBox trouble.
+#
+# XXX jopefuly fixed by adding an event.type().Leave handler to cancel toolTip display XXX
+#
+# y. The dropdown of a combobox does not seem to eat up events
+# An itemView underneath the dropdown still receives mouseMove events
+# and may pop up an itemTip. No idea what todo.
+#
+# y. there is a glitch with comboBoxes. When an itemTip is popped up
+# and the user clicks on it, the dropDown is closed, but the itemTip
+# not. For some reason the itemView eats the mouse message and
+# the itemTip never gets notified.
+#
+# x. there is a possible glitch aswell in itemTips when an itemView gets
+# disabled / hidden while an itemTip is scheduled for display. No way to
+# cancel the itemTip currently
+#
+#
+import os
+from PyQt4 import QtCore, QtGui
+
+__version__ = '0.0.1'
+#***************************************************************************
+#
+#***************************************************************************
+DEFAULT_SHOW_DELAY = 400
+
+#***************************************************************************
+#
+#***************************************************************************
+#code for the toolTip is taken from QToolTip.cpp
+
+class AreaTip(QtGui.QLabel):
+ """
+ Customized tooltip class to show tooltips for a specified area
+ under the mouse cursor.
+
+ """
+
+ def __init__(self, parent, showDelay=DEFAULT_SHOW_DELAY):
+ """constructor
+
+ @param parent parent of the tooltip
+ @showDelay delay in milliseconds before the tootliptp is shown
+ """
+
+
+ QtGui.QLabel.__init__(self, parent, QtCore.Qt.ToolTip)
+ self.hide()
+
+
+ self._currentAreaTipData = None
+ self._showTimer = QtCore.QTimer(self)
+ self._showDelay = showDelay
+
+ self.connect(
+ self._showTimer,
+ QtCore.SIGNAL("timeout()"),
+ self.onShowTip)
+
+
+
+ self.ensurePolished()
+ frameW = self.style().pixelMetric(
+ QtGui.QStyle.PM_ToolTipLabelFrameWidth,
+ None,
+ self
+ )
+ self.setMargin( 1 + frameW)
+ self.setFrameStyle(QtGui.QFrame.NoFrame)
+ self.setAlignment(QtCore.Qt.AlignLeft)
+ self.setIndent(1)
+
+ self.installEventFilter(self)
+
+ opacity = self.style().styleHint(
+ QtGui.QStyle.SH_ToolTipLabel_Opacity,
+ None,
+ self
+ )
+ self.setWindowOpacity( opacity / 255.0)
+ self.setPalette(QtGui.QToolTip.palette())
+
+
+
+ def showDelay(self):
+ """returns the current show delay"""
+ return self._showDelay
+
+ def setShowDelay(self, n):
+ """adjusts the show delay
+ @param n milliseconds to delay the popup of the tooltip
+ """
+ self._showDelay = n
+
+
+ def paintEvent(self, event):
+ """overwritten QWidget.paintEvent"""
+ p = QtGui.QStylePainter(self)
+
+ opt = QtGui.QStyleOptionFrame()
+ opt.init(self)
+ p.drawPrimitive(QtGui.QStyle.PE_PanelTipLabel, opt)
+ p.end()
+
+ QtGui.QLabel.paintEvent(self, event)
+
+
+
+ def eventFilter(self, obj, event):
+ """event filter for the tooltip"""
+
+ type_ = event.type()
+ if type_ == event.KeyPress or type == event.KeyRelease:
+ key = event.key()
+ modifiers = event.modifiers()
+ if modifiers & QtCore.Qt.KeyboardModifierMask or \
+ key == QtCore.Qt.Key_Shift or \
+ key == QtCore.Qt.Key_Control or \
+ key == QtCore.Qt.Key_Alt or \
+ key == QtCore.Qt.Key_Meta:
+ return False
+
+ # TODO: delegate mouseactions to the window underneath the cursor
+ elif type_ in (
+ event.Leave,
+ event.WindowActivate,
+ event.WindowDeactivate,
+ event.MouseButtonPress,
+ event.MouseButtonRelease,
+ event.MouseButtonDblClick,
+ event.FocusIn,
+ event.FocusOut,
+ event.Wheel,
+ ):
+ self.hideTip()
+
+ return False
+
+
+ def onShowTip(self):
+ """called when the tooltip is about to be displayed"""
+
+ if self._currentAreaTipData:
+ pt, text, rc = self._currentAreaTipData
+ pos = QtGui.QCursor().pos()
+ if not rc.contains(pos):
+ return
+
+ desktop = QtGui.QApplication.desktop()
+ if desktop.isVirtualDesktop():
+ scr = desktop.screenNumber(pt)
+ else:
+ scr = desktop.screenNumber(self.parent())
+
+ if os.name == "mac":
+ screen = desktop.availableGeometry(scr)
+ else:
+ screen = desktop.screenGeometry(scr)
+
+ self.setText(text)
+ fm = QtGui.QFontMetrics(self.font())
+ extra = QtCore.QSize(1, 0)
+ # Make it look good with the default ToolTip font on Mac,
+ # which has a small descent.
+ if fm.descent() == 2 and fm.ascent() >= 11:
+ extra.setHeight(extra.height() + 1)
+
+ self.resize(self.sizeHint() + extra)
+
+ self.setWordWrap(QtCore.Qt.mightBeRichText(text))
+ self.move(pt)
+ self.show()
+
+
+ def cancel(self):
+ if self._showTimer.isActive():
+ self._showTimer.stop()
+
+
+ def hideTip(self):
+ """hides the tooltip"""
+ self.cancel()
+ self.hide()
+
+
+ def showText(self, pt, text, rc):
+ """shows the tooltip
+
+ @param pt: (global coordinates) point to show the tooltip at (left/top)
+ @param text: text to show (if emtpty string the tooltip is not shown)
+ @param rc: (global coordinates) bounding rect the tooltip is assigned to
+
+ """
+ self._currentAreaTipData = (pt, text, rc)
+ self.hideTip()
+ if not text.isEmpty():
+ self._showTimer.start(self._showDelay)
+
+
+ def showItemTip(self, itemView, index):
+ """Shows an item tip for item views (QTreeView, QListView ....)
+ for the specified index if necessary.
+
+ @param itemView: item view to display the tip for
+ @param index: QModelIndex of the item to display the tip for
+
+ Note: the tolltip will only be displayed if the text of the item is truncated
+ or the item is not entirely visible.
+ """
+ rc = self.getTruncatedItemRect(itemView, index)
+ if rc is not None:
+ text = index.data().toString()
+ if not text.isEmpty() and itemView.hasFocus():
+ self.showText(rc.topLeft(), text, rc)
+
+
+ def getTruncatedItemRect(self, itemView, index):
+ """Helper method. Returns the rect of a truncted item in an item view
+ @param itemView: QAbstractItemView
+ @param index: index of the item
+ @return: (global coordinates QRect) if the item is truncated, None otherwise
+ @note: this method is callled by showItemTip() to determine wether to
+ display a toolTip for an item or not. Overwrite to customize
+ """
+ viewport = itemView.viewport()
+ rcCli = viewport.contentsRect()
+ rcActual = itemView.visualRect(index)
+ rcDesired = QtCore.QRect(
+ rcActual.topLeft(),
+ itemView.sizeHintForIndex(index)
+ )
+ if not rcActual.contains(rcDesired, False) or \
+ not rcCli.contains(rcDesired, False):
+
+ rcActual.moveTo( viewport.mapToGlobal(rcActual.topLeft()) )
+ return rcActual
+
+
+#****************************************************************************
+#
+#****************************************************************************
+class ItemTips(QtCore.QObject):
+ """Equips an itemView (QTreeView, QListView ....) with tooltips for truncated items (item tips)
+ """
+
+ def __init__(self, itemView, showDelay=DEFAULT_SHOW_DELAY):
+ """
+ @param itemView: itemView to equip with item tips
+ param showDelay: delay in (miliseconds) after wich to show a tooltip for an item
+ """
+ QtCore.QObject.__init__(self, itemView)
+
+ self.areaTip = AreaTip(itemView, showDelay=showDelay)
+ self.itemView = itemView
+
+ itemView.setMouseTracking(True) # enshure we get mouseMove messages
+ itemView.viewport().installEventFilter(self)
+
+
+ def eventFilter(self, obj, event):
+ """Event filter for the itemTip"""
+ if event.type() == event.MouseMove:
+ if self.areaTip.isHidden():
+
+ # hope this check fixes a glitch with comboBoxes. Sometimes am itemTip
+ # did pop up when the combos dropdown was closed.
+ if not self.itemView.isHidden() and self.itemView.isEnabled() and self.itemView.underMouse():
+ index = self.itemView.indexAt(event.pos())
+ if index.isValid():
+ self.areaTip.showItemTip(self.itemView, index)
+ if event.type() == event.Leave:
+ self.areaTip.cancel()
+
+ return False
+
+
+ def setEnabled(self, flag):
+ self.areaTip.setEnabled(flag)
+ pass
+
+ def isEnabled(self):
+ self.areaTip.isEnabled()
+
+ def getShowDelay(self):
+ """returns the current show delay"""
+ return self.areaTip.getShowDelay()
+
+ def setShowDelay(self, n):
+ """adjusts the show delay
+ @param n milliseconds to delay the popup of the tooltip
+ """
+ return self.areaTip.setShowDelay(delay)
+
+#****************************************************************************
+#
+#****************************************************************************
+class ComboBoxEditTips(QtCore.QObject):
+ """Equips a QComboBox with a toolTip popping up when the text in its edit box is truncated
+
+ @note: this class does currently not quite what it is supposed todo. A normal toolTip is
+ sisplayed instead of a toolTip that covers the editBox. This may change in future versions.
+ """
+
+ def __init__(self, comboBox, showDelay=DEFAULT_SHOW_DELAY):
+ QtCore.QObject.__init__(self, comboBox)
+
+ self.connect(
+ comboBox,
+ QtCore.SIGNAL('currentIndexChanged(const QString &)'),
+ self._adjustToolTip
+ )
+
+ comboBox.installEventFilter(self)
+ self.comboBox = comboBox
+
+
+ def eventFilter(self, obj, event):
+ if event.type() == event.Resize:
+ self._adjustToolTip(self.comboBox.currentText())
+ return False
+
+ def _adjustToolTip(self, text):
+ fm = self.comboBox.fontMetrics()
+ style = self.comboBox.style()
+ rc = self.comboBox.contentsRect()
+ rc2 = style.subControlRect(
+ style.CC_ComboBox,
+ QtGui.QStyleOptionComboBox(),
+ style.SC_ComboBoxEditField,
+ )
+
+ cx = rc.width() + rc2.width()
+ w = fm.width(text)
+ if w > cx -2:
+ self.comboBox.setToolTip(text)
+ else:
+ self.comboBox.setToolTip('')
+
+
+
+
+#********************************************************************************************
+# some test guis
+#********************************************************************************************
+def _testItemModel():
+ """Tests itemTips for a QTableView"""
+
+ import sys
+
+
+ class TestModel(QtCore.QAbstractTableModel):
+
+
+ def __init__(self, table):
+ QtCore.QAbstractTableModel.__init__(self, table)
+
+ self.__table = table
+ self.__columns = ('foo', "foo")
+ self.__rows = ['foo'*10 for i in range(10)]
+
+ self.areaTip = AreaTip(table)
+
+
+ def hasIndex(self, row, column):
+ """QAbstractTableModel.hasIndex() implementation"""
+ if -1 < column <= len(self.__columns):
+ if -1 < row < len(self.__rows):
+ return True
+ return False
+
+ def rowCount(self, parent):
+ """QAbstractTableModel.rowCount() implementation"""
+ return len(self.__rows)
+
+
+ def columnCount(self, parent):
+ """QAbstractTableModel.columnCount() implementation"""
+ return len(self.__columns)
+
+
+ def headerData(self, section, orientation, role):
+ """QAbstractTableModel.headerData() implementation"""
+ if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
+ return QtCore.QVariant(self.__columns[section])
+ return QtCore.QVariant()
+
+
+ def data(self, index, role):
+ """QAbstractTableModel.data() implementation"""
+
+ if self.hasIndex(index.row(), index.column()):
+ if role == QtCore.Qt.DisplayRole:
+ return QtCore.QVariant(self.__rows[index.column()])
+
+ # display tooltip if text does not fit column width
+ # or item is not entirely visible
+ elif role == QtCore.Qt.ToolTipRole:
+ self.areaTip.showItemTip(self.__table, index)
+
+
+ return QtCore.QVariant()
+
+ app = QtGui.QApplication(sys.argv)
+ dlg = QtGui.QWidget()
+
+ grid = QtGui.QHBoxLayout(dlg)
+
+ tv = QtGui.QTableView(dlg)
+ grid.addWidget(tv)
+
+ tv.setModel(TestModel(tv))
+
+ dlg.show()
+ res = app.exec_()
+ sys.exit(res)
+
+
+
+def _testComboBox():
+ """Tests itemTips for a QCombobox"""
+
+
+ import sys
+
+ app = QtGui.QApplication(sys.argv)
+ w = QtGui.QWidget()
+
+ b = QtGui.QGridLayout(w)
+ c = QtGui.QComboBox(w)
+ c.setMinimumContentsLength(5)
+ b.addWidget(c)
+
+ itemTips = ItemTips(c.view())
+
+
+ for i in range(10):
+ c.addItem('loooooooooooooooooooooooooooooong-itemText-%s' % i)
+
+ w.show()
+ res = app.exec_()
+ sys.exit(res)
+
+
+
+#***********************************************************************
+#
+#***********************************************************************
+if __name__ == "__main__":
+ pass
+ #_testItemModel()
+ #_testComboBox()
+
+
+
Added: trunk/fclient/fclient_lib/qt4ex/ctrls/checkarraywrap.py
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/ctrls/checkarraywrap.py (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/ctrls/checkarraywrap.py 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1,93 @@
+"""wrapper class for handling checkboxes (...) as bit array
+
+
+CONST_1 = 1
+CONST_2 = 2
+CONST_3 = 4
+
+checks = {
+ CONST_1: ckeckbox1,
+ CONST_2: checkbox2,
+ CONST_3: cjeckbox3,
+
+ }
+
+
+flags = CheckArray(checks, initvalue=CONST_1 | CONST_2)
+
+flags |= CONST_3 # check 3rd checkbox
+flags.showChecks(CONST_1 | CONST_2) # hide 3rd checkbox
+
+"""
+
+from PyQt4 import QtCore
+#*******************************************************
+#
+#*******************************************************
+class CheckArrayWrap(QtCore.QObject):
+
+ def __init__(self, parent, checks, value=None):
+ QtCore.QObject.__init__(self, parent)
+ self._checks = checks
+
+ if value is not None:
+ self.setChecks(value)
+
+ ######################################
+ ## public methods
+ ######################################
+ def getValue(self):
+ value = 0
+ for const, ck in self._checks.items():
+ if ck.isChecked():
+ value |= const
+ return value
+
+
+ def setChecks(self, flags):
+ for const, ck in self._checks.items():
+ if flags & const:
+ ck.setCheckState(QtCore.Qt.Checked)
+ else:
+ ck.setCheckState(QtCore.Qt.Unchecked)
+ flags &= ~const
+ if not flags:
+ break
+
+
+ def showChecks(self, flags):
+ for const, ck in self._checks.items():
+ if flags & const:
+ check.control.setEnabled(True)
+ check.control.show()
+ else:
+ check.control.setEnabled(False)
+ check.control.hide()
+ flags &= ~const
+ if not flags:
+ break
+
+
+ def __or__(self, n):
+ value = self.getValue() | n
+ self.setChecks(value)
+ return self
+ def __ior__(self, n): return self.__or__(n)
+ def __ror__(self, n): return self.__or__(n)
+
+ def __and__(self, n):
+ value = self.getValue() & n
+ self.setChecks(value)
+ return self
+ def __iand__(self, n): return self.__and__(n)
+ def __rand__(self, n): return self.__and__(n)
+
+ def __xor__(self, n):
+ value = self.getValue() ^ n
+ self.setChecks(value)
+ return self
+ def __ixor__(self, n): return self.__xor__(n)
+ def __rxor__(self, n): return self.__xor__(n)
+
+
+
Added: trunk/fclient/fclient_lib/qt4ex/ctrls/colorbutton.py
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/ctrls/colorbutton.py (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/ctrls/colorbutton.py 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1,238 @@
+"""pyQt4 ColorButton with drag and drop support
+
+mostly taken from
+[http://api.kde.org/4.0-api/kdelibs-apidocs/kdeui/html/classKColorButton.html]
+with some minor adjustements
+"""
+
+import sys, os
+
+#--> rel import hack
+d = os.path.dirname(os.path.dirname(__file__))
+sys.path.insert(0, d)
+
+import qtools
+
+sys.path.pop(0)
+del d
+#<-- rel import hack
+
+
+from PyQt4 import QtCore, QtGui
+
+__author__ = 'Juergen Urner'
+__email__ = 'jU...@ar...'
+__version__ = '0.1.0'
+#************************************************************************
+#
+#************************************************************************
+class ColorButtonWrap(QtCore.QObject):
+ """Event filter to transform a QPushButton into a color button
+
+ The button will popup a color selection dialog when hit ++ the button supports
+ drag / drop of colors.
+
+ @signal colorChanged(const QColor*): emited when the user changes the color of
+ the button via color dialog or color drop.
+ """
+
+ def __init__(self, button, color=None, fitColor=True):
+ """
+ @param button: button to wrap
+ @param color: initial color. If None, initial color is black
+ @fitColor: if True the color shown is adjusted to fit the contents of the button
+ """
+ QtCore.QObject.__init__(self, button)
+
+ self.button = button
+ self._color = color or QtGui.QColor('black')
+ self.fitColor = fitColor
+
+ button.setAcceptDrops(True)
+ button.installEventFilter(self)
+ self.connect(button, QtCore.SIGNAL('clicked()'), self.chooseColor)
+
+
+ def _initStyleOption(self, opt):
+ opt.initFrom(self.button)
+ opt.text.clear()
+ opt.icon = QtGui.QIcon()
+ opt.features = QtGui.QStyleOptionButton.None
+
+
+ def eventFilter(self, obj, event):
+ """Event filter for the button wrapped"""
+
+ eventType = event.type()
+
+ if eventType == event.Paint:
+ painter = QtGui.QPainter(self.button)
+
+ # draw bevel
+ opt = QtGui.QStyleOptionButton()
+ self._initStyleOption(opt)
+ self.button.style().drawControl(QtGui.QStyle.CE_PushButtonBevel, opt, painter, self.button)
+
+ # draw color box
+ #First, sort out where it goes
+ labelRect = self.button.style().subElementRect(QtGui.QStyle.SE_PushButtonContents, opt, self.button)
+ shift = self.button.style().pixelMetric(QtGui.QStyle.PM_ButtonMargin)
+ labelRect.adjust(shift, shift, -shift, -shift);
+ x, y, w, h = labelRect.getRect()
+
+ if self.button.isChecked() or self.button.isDown():
+ x += self.button.style().pixelMetric(QtGui.QStyle.PM_ButtonShiftHorizontal)
+ y += self.button.style().pixelMetric(QtGui.QStyle.PM_ButtonShiftVertical)
+ if self.button.isEnabled():
+ fillCol = self.getColor()
+ else:
+ fillCol = self.button.palette().color(self.button.backgroundRole())
+
+ QtGui.qDrawShadePanel(painter, x, y, w, h, self.button.palette(), True, 1, None)
+ if fillCol.isValid():
+ painter.fillRect( x+1, y+1, w-2, h-2, fillCol)
+
+ if self.button.hasFocus():
+ focusRect = self.button.style().subElementRect(QtGui.QStyle.SE_PushButtonFocusRect, opt, self.button)
+ focusOpt = QtGui.QStyleOptionFocusRect()
+ focusOpt.initFrom(self.button)
+ focusOpt.rect = focusRect
+ focusOpt.backgroundColor = self.button.palette().background().color()
+ self.button.style().drawPrimitive(QtGui.QStyle.PE_FrameFocusRect, focusOpt, painter, self.button)
+ return True
+
+ elif eventType == event.MouseMove:
+ if not self._mouse_pressed:
+ return False
+
+ if event.buttons() & QtCore.Qt.LeftButton:
+ if (self._mousepress_pos - event.pos()).manhattanLength() > QtGui.QApplication.startDragDistance():
+ self._mouse_pressed = False
+ self.button.setDown(False)
+ drag = QtGui.QDrag(self.button)
+ data = QtCore.QMimeData()
+ data.setColorData(QtCore.QVariant(self._color))
+ drag.setMimeData(data)
+ drag.start(QtCore.Qt.CopyAction)
+ return True
+
+ elif eventType == event.MouseButtonPress:
+ self._mousepress_pos = QtCore.QPoint(event.pos())
+ self._mouse_pressed = True
+ return False
+
+ elif eventType == event.MouseButtonRelease:
+ self._mouse_pressed = False
+ return False
+
+ elif eventType == event.DragEnter:
+ if event.mimeData().hasColor():
+ event.accept()
+ else:
+ event.ignore()
+ return True
+
+ elif eventType == event.DragMove:
+ if event.mimeData().hasColor():
+ event.accept()
+ else:
+ Event.ignore()
+ return True
+
+ elif eventType == event.Drop:
+ if event.mimeData().hasColor():
+ v = event.mimeData().colorData()
+ self.setColor( QtGui.QColor(v.toString() ) )
+ else:
+ event.ignore()
+ return True
+
+
+ # TODO: copy & paste
+ if eventType == event.KeyPress:
+
+ if qtools.isStandardKeyEvent(event, QtGui.QKeySequence.Copy):
+ mimeData = QtCore.QMimeData()
+ mimeData.setColorData(QtCore.QVariant(self.getColor() ) )
+ QtGui.QApplication.clipboard().setMimeData(mimeData, QtGui.QClipboard.Clipboard)
+ elif qtools.isStandardKeyEvent(event, QtGui.QKeySequence.Paste):
+ mimeData = QtGui.QApplication.clipboard().mimeData(QtGui.QClipboard.Clipboard)
+ if mimeData.hasColor():
+ v = mimeData.colorData()
+ self.setColor( QtGui.QColor(v.toString() ) )
+
+
+
+
+
+ """
+ void KColorButton::keyPressEvent( QKeyEvent *e )
+ {
+ int key = e->key() | e->modifiers();
+
+ if ( KStandardShortcut::copy().contains( key ) ) {
+ QMimeData *mime=new QMimeData;
+ KColorMimeData::populateMimeData(mime,color());
+ QApplication::clipboard()->setMimeData( mime, QClipboard::Clipboard );
+ }
+ else if ( KStandardShortcut::paste().contains( key ) ) {
+ QColor color=KColorMimeData::fromMimeData( QApplication::clipboard()->mimeData( QClipboard::Clipboard ));
+ setColor( color );
+ }
+ else
+ QPushButton::keyPressEvent( e );
+ }
+ """
+
+ return False
+
+
+ #############################################
+ ## methods
+ #############################################
+ def chooseColor(self):
+ """Pops up a color selection dialog to adjust the color of the button"""
+ color = QtGui.QColorDialog.getColor(self._color, self.button)
+ if color.isValid():
+ self.setColor(color)
+ self.emit(QtCore.SIGNAL('colorChanged(const QColor*)'), color)
+
+ def getColor(self):
+ """Returns the color of the button
+ @return: QColor
+ """
+ return self._color
+
+ def setColor(self, color):
+ """Sets the color of the button
+ @param color: QColor
+ """
+ self._color = color
+ self.button.update()
+ self.emit(QtCore.SIGNAL('colorChanged(const QColor*)'), color)
+
+
+#********************************************************************
+#
+#********************************************************************
+if __name__ == '__main__':
+ import sys
+
+ app = QtGui.QApplication(sys.argv)
+ w = QtGui.QWidget(None)
+ box = QtGui.QVBoxLayout(w)
+ colors = ('yellow', 'red', 'green', 'blue')
+ for color in colors:
+ bt = QtGui.QPushButton(w)
+ wrap = ColorButtonWrap(bt, QtGui.QColor(color))
+ box.addWidget(bt)
+
+
+
+ w.show()
+ res = app.exec_()
+ sys.exit(res)
+
+
+
+
\ No newline at end of file
Added: trunk/fclient/fclient_lib/qt4ex/ctrls/compactpatwrap.py
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/ctrls/compactpatwrap.py (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/ctrls/compactpatwrap.py 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1,576 @@
+
+"""module to handles compacting of filepaths and urls
+
+ @author Juergen Urner
+ """
+
+import operator
+import os
+import re
+
+import ntpath
+import posixpath
+import macpath
+
+from PyQt4 import QtCore, QtGui
+
+# TODO:
+#
+# x. builtin type() is shaddowed in some methods
+# x. rename DEFAULT to DEFAULT_TYPE
+
+#*******************************************************************************
+# compacts a filepath to fit into a desired width
+# As measuring function any function may be passed that returns True if a path
+# is short enough, False otherwise. Default is len()
+#
+#
+# IDEA:
+#
+# weight path components by priority, 0 is lowest.
+#
+# 2 0 1 3
+# mypath = "aaa/bb/ccc/ddd"
+#
+# genearte a sort matrix [[nPriority0, indexComponent0, component0], [...]]
+# so we only have to flip the matrix to either get the priority order of the
+# components and eat components from martx[0] to matrix[N] or the restore
+# the original component order
+#
+# matrix = [[2, 0, "aaa"], [0, 1, "bbb"], [1, 2, "ccc"], [3, 3, "ddd"]]
+# matrix.sort()
+# matrix = [[0, 1, "bbb"], [1, 2, "ccc"], [2, 0, "aaa"], [3, 3, "ddd"]]
+#
+# I am shure that so. with better algo skills than me can come up with a nicer
+# solution to the problem. I would like to see that.
+#
+#**********************************************************************************
+
+#**************************************************************************************
+# helpers
+#**************************************************************************************
+_drive_pat = re.compile(r'([a-zA-Z]\:)(?=/|\\|\Z)')
+def is_drive(fpath):
+ return bool(_drive_pat.match(fpath))
+
+_host_pat = re.compile(r'([^:/?#]+:[^/]*///?)')
+def is_host(fpath):
+ return bool(_host_pat.match(fpath))
+
+def is_root(fpath):
+ return fpath.startswith("/")
+
+def is_mac_root(fpath):
+ if fpath:
+ return ':' in fpath and fpath[0] != ':'
+ return False
+
+if os.path.__name__.startswith("nt"):
+ DEFAULT = "nt"
+elif os.path.__name__.startswith("mac"):
+ DEFAULT = "mac"
+elif os.path.__name__.startswith("posix"):
+ DEFAULT = "posix"
+else:
+ DEFAULT = os.path.__name__
+
+PATH_MODULES = { # modules to handle path actions for path types
+ 'nt': ntpath,
+ 'posix': posixpath,
+ 'mac': macpath,
+ 'url': posixpath,
+ DEFAULT: os.path,
+ }
+
+#**************************************************************************************
+#
+#**************************************************************************************
+class PathCompacter(object):
+ """
+ implementation of the compact path algorithm.
+
+ The class is available as compactPath() function in the module.
+ For a description of parameters see PathCompacter.__call__()
+
+ @note: you do not have to use this class directly. On the module level
+ its functionality is available as function compactPath()
+ """
+
+ ELLIPSIS = "..."
+ PARDIR = ".."
+ DOT = "."
+
+ def __init__(self):
+ """
+ constructor
+ """
+ self.root = ""
+ self.path_module = None
+
+
+ def chewComponent(self, cpn, fromLeft=True):
+ """
+ shorten a single path component by one char
+
+ @param cpn the component to to be shortened
+ @keyparam fromLeft if True the component is shortened on the left side,
+ if False on the right
+ """
+ if fromLeft:
+ while True:
+ cpn = cpn[3: ]
+ if not cpn:
+ yield self.PARDIR
+ break
+
+ cpn = self.PARDIR + cpn
+ yield cpn
+
+ else:
+ while True:
+ cpn = cpn[ :-3]
+ if not cpn:
+ yield self.PARDIR
+ break
+ cpn = cpn + self.PARDIR
+ yield cpn
+
+
+ def getMatrix(self, n):
+ """
+ generates a sort matrix of lenght (n)
+ """
+ if n == 0:
+ return []
+
+ # ...enshure last item in range is last item in the matrix
+ if n % 2:
+ rng = range(n)
+ else:
+ rng = range(n -1)
+
+ start = len(rng) / 2
+ out = []
+ for i in rng:
+ if i == start:
+ out.append([0, ])
+ elif i < start:
+ out.append([ (start - i) *2 - 1, ])
+ else:
+ out.append([ (i - start) *2, ])
+
+ if not n % 2:
+ out.append([len(out)])
+ return out
+
+
+ def matrixToPath(self, matrix):
+ """
+ private method to generate a filepath from a sort matrix
+ """
+ getter = operator.itemgetter
+ out = []
+
+ matrix.sort(key=getter(1))
+ for i in matrix:
+ out.append(i[2])
+
+ matrix.sort()
+
+ if self.type == "url":
+ return self.root + "/".join(out)
+ elif self.type == "nt":
+ return self.path_module.join(self.root, *out)
+ elif self.type == "posix":
+ return self.path_module.join(self.root, *out)
+ elif self.type == "mac":
+ return self.path_module.join(self.root, *out)
+ return self.path_module.join(*out)
+
+
+ def matrixFromPath(self, fpath):
+ """
+ private method to convert a filepath into a sort matrix
+ """
+ arrPath = self._path_to_list(fpath)
+ if not arrPath:
+ return []
+
+ self.root = ""
+ if self.type == "nt":
+ if is_drive(arrPath[0]):
+ self.root = arrPath[0]
+ arrPath.pop(0)
+ elif self.type == "posix":
+ if is_root(arrPath[0]):
+ self.root = arrPath[0]
+ arrPath.pop(0)
+ elif self.type == "mac":
+ if is_mac_root(arrPath[0]):
+ self.root = arrPath[0]
+ arrPath.pop(0)
+ elif self.type == "url":
+ host = arrPath[0] + "//"
+ if is_host(host):
+ self.root = host
+ arrPath.pop(0)
+
+ matrix = self.getMatrix(len(arrPath))
+ i = 0
+ for item in matrix:
+ item.append(i)
+ item.append(arrPath[i])
+ i += 1
+
+ return matrix
+
+
+ def _path_to_list(self, fpath):
+ head = fpath
+ out = []
+ # taken from: http://www.jorendorff.com/articles/python/path/
+ while head != self.path_module.curdir and head != self.path_module.pardir:
+ prev = head
+ head, tail = self.path_module.split(head)
+ if head == prev: break
+ out.append(tail)
+ if head:
+ out.append(head)
+ out.reverse()
+ return out
+
+
+ def __call__(self, fpath, w, measure=len, max_pardirs=2, type=DEFAULT):
+ """
+ compacts a filepath or url to fit into the desired width
+
+ @param fpath the filepath to be compacted
+ @param w the desired width the filepath should fit into
+ @keyparam measure function to measure the length of the filepath.
+ Default is len() but may be any other function that takes a filepath as
+ argument and returns its length as undigned int.
+ @keyparam max_pardirs maximum number of compacted parent dirs ("../") allowed
+ in the compacted path. Must be > 0.
+ @keyparam type use this explicitely specify the type of path passed.
+ Can be "posix", "nt", "mac" or "url" or DEFAULT. DEFAULT processes the
+ path with whatever os.path currently is.
+
+ """
+ if max_pardirs < 1:
+ raise PathError("max_pardirs < 1 is not allowed")
+
+ if not fpath:
+ return ""
+ if measure(fpath) < w:
+ return fpath
+
+ self.type, self.path_module = type, PATH_MODULES[type]
+ matrix = self.matrixFromPath(fpath)
+ if not matrix:
+ return "" # error here, the pattern does not match anything
+ matrix.sort()
+
+ # process our sort matrix
+ i = 0
+ while True:
+
+ if len(matrix) > 2:
+ # ...chew next component till it's exhausted
+ item = matrix[i]
+ for cpn in self.chewComponent(item[2], fromLeft=False):
+ item[2] = cpn
+ path = self.matrixToPath(matrix)
+ if measure(path) < w:
+ return path
+
+ if cpn == self.PARDIR:
+ i += 1
+ break
+
+ # ...pop 1 pardir if necessary
+ if i > max_pardirs or i >= len(matrix) -1:
+ matrix.pop(0)
+ matrix[0][2] = self.ELLIPSIS
+ i -= 1
+ path = self.matrixToPath(matrix)
+ if measure(path) < w:
+ return path
+
+ else:
+ # finalize
+ # 1. chew root away
+ if self.root:
+ for cpn in self.chewComponent(self.root, fromLeft=True):
+ self.root = cpn
+ if self.root == self.PARDIR:
+ if not matrix:
+ if measure(self.PARDIR) < w:
+ return self.PARDIR
+ if measure(self.DOT) < w:
+ return self.DOT
+ return ""
+ self.root = ""
+ break
+ path = self.matrixToPath(matrix)
+ if measure(path) < w:
+ return path
+
+ path = self.matrixToPath(matrix)
+ if measure(path) < w:
+ return path
+
+ # 2. chew filename away
+ if len(matrix) == 2:
+ component = matrix[0][2] + matrix[1][2]
+ if measure(component) < w:
+ return component
+ else:
+ component = matrix[0][2]
+
+ for cpn in self.chewComponent(component, fromLeft=True):
+ if measure(cpn) < w:
+ return cpn
+
+ if cpn == self.PARDIR:
+ if measure(self.DOT) < w:
+ return self.DOT
+ break
+
+ # done it
+ break
+
+ return ""
+
+
+#********************************************
+# init PathCompacter() as function
+#********************************************
+compactPath = PathCompacter()
+#********************************************************************************
+# lightweight wrapper class for QLabels to display a compactable filepath or url
+# the class is designed as a wrapper not a derrived class so it does not get in the
+# way with QtDesigner
+#
+# problem to tackle is that every setText() triggers a resize of the whole layout
+# so the filepath can't be adjusted in resizeEvent() (recursive). We handle
+# paintEvent() instead and draw the label from scratch.
+#
+# similar wrappers can be easily implemented for QMenuItem() and QListView()
+# header controls (...).
+#
+# Usage hint:
+# if used on statusbars you should adjust the stretch factor, otherwise
+# the label might appear invisible, 'cos it may not get resized.
+#
+# The label has a minimum size of 0 by default. To force a minimum
+# size set any initial text to the label wrapped. Effect is 1. the text will not be
+# displayed ++ the label will not get resized any smaller than this text.
+#
+#*******************************************************************************
+class PathLabelWrap(object):
+ """
+ class wrapping a QLabel to display a filepath or url that is compacted on
+ resizing of the label
+ """
+
+ def __init__(self, label, fpath="", prefix="", max_pardirs=1, type=DEFAULT):
+ """
+ constructor
+
+ @param label an initialized QLabel to wrap compactPath functionality around
+ @param fpath the filepath or url the label should display
+ @keyparam prefix: chars to be used as fixed prefix of the path like: "file: my/path/here"
+ @keyparam max_pardirs maximum number of compacted parent dirs ("../") allowed
+ in the compacted path. Must be > 0.
+ @keyparam type: use this to explicitely specify the type of path specified.
+ Can be "nt", "posix", "mac", "url" or DEFAULT to use whatever type of path
+ os.path currently handles.
+
+ Note: currently the wrapper does not know how to display text for disabled state.
+ So disabling the underlaying QLabel will not result in the desired effect.
+ """
+ self.label = label
+ self.fpath = fpath
+ self.prefix = prefix
+ self.max_pardirs= max_pardirs
+ self.type = type
+
+ self.label.paintEvent = self.onPaint # overwrite
+ # for testing:
+ #self.label.setPaletteBackgroundColor(qt.QColor(10, 255, 22))
+
+
+ def onPaint(self, event):
+ """
+ overwritten method to handle painting of the filepath
+ """
+
+ # draw the label from scratch
+ # TODO: check if there is an easier processing via QStyle.drawControl()
+ fm = self.label.fontMetrics()
+ rc = self.label.rect()
+ frameW = self.label.frameWidth()
+ indent = self.label.indent()
+ if indent < 0: # see Qt docs: label.indent()
+ if frameW > 0:
+ indent = fm.width("x") /2
+ else:
+ indent = 0
+ rc.adjust(frameW + indent, frameW , -(frameW + indent), -frameW)
+
+ w = rc.width()
+ if self.prefix:
+ w -= fm.width(self.prefix)
+
+ fpath = compactPath(
+ self.fpath,
+ w,
+ measure=fm.width,
+ max_pardirs=self.max_pardirs,
+ type=self.type
+ )
+
+ painter = QtGui.QPainter(self.label)
+ painter.eraseRect(self.label.rect())
+ self.label.drawFrame(painter)
+ # TODO: draw enabled/disabled text
+ # textColor is already ok but I haven't found a way to draw disabled text
+ # embossed
+ # if self.label.isEnabled():
+ # else:
+ if self.prefix:
+ painter.drawText(rc, self.label.alignment(), '%s%s' % (self.prefix, fpath))
+ else:
+ painter.drawText(rc, self.label.alignment(), fpath)
+
+
+ def setPath(self, fpath, prefix=None, max_pardirs=None, type=None):
+ """
+ sets the filepath or url to be displayed
+
+ If any of the keyword params is not None, the according property is adjusted to the specified value
+ """
+ if prefix is not None:
+ self.prefix = prefix
+ if max_pardirs is not None:
+ self.max_pardirs = max_pardirs
+ if type is not None:
+ self.type = type
+ self.fpath = fpath
+ self.label.update()
+
+ def getPath(self):
+ """
+ retrieves the (uncompacted) filepath or url
+ """
+ return self.fpath
+
+#******************************************************************************
+# tests
+#******************************************************************************
+if __name__ == "__main__":
+
+ def testGui():
+
+ CAPTION = "compactPath - [%s]"
+ PATHS = (
+ ('posix', "/my/very/long/path/containing/many/compoponents/here.txt"),
+ ('nt', "c:\\my\\very\\long\\path\\containing\\many\\compoponents\\here.txt"),
+ ('mac', "my:very:long:path:containing:many:compoponents:here.txt"),
+ ('url', "http://my/very/long/path/containing/many/compoponents/here.txt"),
+ )
+ BLINDTEXT = "xxxxxxxxxx" # this is set as text to the labels to force a minimum size. Adjust to your needs
+
+ class TestGui(QtGui.QMainWindow):
+ """test gui"""
+
+ def __init__(self):
+ QtGui.QMainWindow.__init__(self)
+
+ self.mainWidget = QtGui.QWidget(self)
+ self.setCentralWidget(self.mainWidget)
+
+ # NOTE: all the sep="" params are only thrown in for test purposes.
+ #
+ # Too bad, the label in the caption bar seems not to be available in Qt. So there is no way
+ # to adjust it dynamically. So init to some fixed width. Note that the width of the caption
+ # bar label has no effect on the GUIs width.
+ #
+ pathType, fpath = PATHS[0]
+ fpath = compactPath(fpath, 50 - len(CAPTION), type=pathType)
+ self.setWindowTitle(CAPTION % fpath)
+
+ # throw labels into the Gui
+ layout = QtGui.QVBoxLayout(self.centralWidget())
+ frameStyle = QtGui.QLabel.Sunken | QtGui.QLabel.Box
+ self.pathLabels = []
+ for pathType, fpath in PATHS:
+ label = QtGui.QLabel(BLINDTEXT, self.mainWidget)
+ w = PathLabelWrap(label, fpath=fpath, prefix=pathType + ': ', type=pathType)
+ label.setFrameStyle(frameStyle)
+ layout.addWidget(label)
+
+ # just a test label to see if our owner drawn labels are ok
+ self.labelTest = QtGui.QLabel("just a test", self.mainWidget)
+ self.labelTest.setFrameStyle(frameStyle)
+ layout.addWidget(self.labelTest)
+
+ layout.addItem(QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Expanding))
+
+ # add another one on the statusbar
+ self.status = self.statusBar()
+ self.statusLabelPosix = PathLabelWrap(QtGui.QLabel(BLINDTEXT, self.status), fpath=fpath)
+ self.status.addWidget(QtGui.QLabel('status::', self.status))
+ self.status.addWidget(self.statusLabelPosix.label, 10)
+ self.status.addWidget(QtGui.QLabel('foo-bar', self.status))
+
+ def styleChange(self, oldStyle):
+ """styleChange handler"""
+ self.update()
+
+ import sys
+ a = QtGui.QApplication(sys.argv)
+ QtCore.QObject.connect(a,QtCore.SIGNAL("lastWindowClosed()"),a,QtCore.SLOT("quit()"))
+ w = TestGui()
+ w.show()
+ a.exec_()
+
+ ##
+ testGui()
+
+
+
+ def test():
+ """
+ test compactPath()
+ """
+
+ def compact(path, start, stop, type=DEFAULT):
+ for i in range(start +1, stop, -1):
+ p = compactPath(path, i, max_pardirs=1, type=type)
+ print i, len(p), repr(p)
+
+ def test1():
+ path = "a:\\eeeeee\\fff\\foobarbaz\\ddd"
+ compact(path, len(path), 0, type="nt")
+ test1()
+
+ def test2():
+ path = "/home/eeeeee/fff/foobarbaz/ddd"
+ compact(path, len(path), 0, type="posix")
+ test2()
+
+ def test3():
+ path = "home:eeeeee:fff:foobarbaz:ddd"
+ compact(path, len(path), 0, type="mac")
+ test3()
+
+ def test4():
+ path = "home://eeeeee/fff/foobarbaz/ddd"
+ compact(path, len(path), 0, type="url")
+ test4()
+
+ ##
+ #test()
+
+
Added: trunk/fclient/fclient_lib/qt4ex/ctrls/dragtool.py
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/ctrls/dragtool.py (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/ctrls/dragtool.py 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1,205 @@
+"""DraggableTool widget"""
+
+
+from PyQt4 import QtCore, QtGui
+
+__version__ = '0.0.4'
+#**********************************************************************
+#
+#**********************************************************************
+class DragToolEventFilter(QtCore.QObject):
+ """helper widget to handle draging of bitmaps as cursor
+
+ @note: the widget has to be visible and at least 1x1 in size, otherwise Qt refuses to
+ pass the mouse capture over to the widget
+ """
+
+
+ def __init__(self, button, cursor=None, releaseOnClick=False):
+ """constructor
+
+ @arg parent: parent of the grabber widget
+ @arg cursor: QCursor
+ @arg grab_cursor: if 'grab_cursor' is True the user may drag the pixmap around
+ without holding the left mouse button. If False the cursor is released as soon as
+ the user releases the the left mouse button. In both cases dragging can be endet
+ by hitting the escape key. In "grab_cursor" mode dragging may be endet aswell by
+ hitting the right mouse button.
+
+ """
+ QtGui.QWidget.__init__(self, button)
+
+ self._isDraging = False
+ self._releaseOnClick = releaseOnClick
+
+ self.button = button
+ self.cursor = None
+
+ self.setCursor(cursor)
+ self.button.installEventFilter(self)
+ button.connect(button, QtCore.SIGNAL('clicked()'), self.startDrag)
+
+
+ def getReleaseOnClick(self):
+ return self._releaseOnClick
+
+ def setReleaseOnClick(self, flag):
+ self._releaseOnClick = flag
+
+
+ def isDraging(self):
+ """Checks if the drag tool is currently about to be draged"""
+ return self._isDraging
+
+
+ def startDrag(self):
+ """Starts dragging the cursor around"""
+
+ if self.cursor is None:
+ raise ValueError('No cursor set')
+
+ if not self.isDraging():
+ self._isDraging = True
+ #self.button.grabKeyboard()
+ self.button.grabMouse(self.cursor)
+ self.button.setMouseTracking(True)
+ pt = QtGui.QCursor().pos()
+ self.emit(QtCore.SIGNAL('startDrag(int, int)'), pt.x(), pt.y())
+
+
+ def getCursor(self):
+ """Returns the dragg cursor
+ @return: QCursor
+ """
+ return self.cursor
+
+
+ def setCursor(self, cursor):
+ """Sets the pixmap to be dragged around
+ @arg cursor: QCursor
+ """
+ self.cursor = cursor
+
+
+ def setGrabCursor(self, flag):
+ self.grab_cursor = flag
+
+ def getGrabCursor(self, flag):
+ return self.grab_cursor
+
+
+ def endDrag(self, x=0, y=0, canceled=False):
+ if self.isDraging():
+ self._isDraging = False
+ #self.button.releaseKeyboard()
+ self.button.releaseMouse()
+ self.button.setMouseTracking(False)
+ if canceled:
+ self.emit(QtCore.SIGNAL('dragCanceled()'))
+ else:
+ self.emit(QtCore.SIGNAL('draged(int, int)'), x, y)
+ self.emit(QtCore.SIGNAL('dragEnd()'))
+
+
+ def eventFilter(self, obj, event):
+
+ # TODO: grabKeyboard() will eat all keyboard input for a Gui but events never arrive here
+ #if event.type() == event.KeyRelease:
+ # if event.key() == QtCore.Qt.Key_Escape:
+ # self.endDrag(canceled=True)
+ # return True
+
+ if event.type() == event.MouseButtonPress:
+ if event.button()== QtCore.Qt.LeftButton:
+ if self.isDraging():
+ if self._releaseOnClick:
+ self.endDrag(event.globalX(), event.globalY(), canceled=False)
+ return True
+
+ elif event.type() == event.MouseButtonRelease:
+ if event.button()== QtCore.Qt.LeftButton:
+ if self.isDraging():
+ if not self._releaseOnClick:
+ self.emit(QtCore.SIGNAL('draged(int, int)'), event.globalX(), event.globalY())
+ return True
+ elif event.button()== QtCore.Qt.RightButton:
+ if self.isDraging():
+ self.endDrag(canceled=True)
+ return True
+
+ elif event.type() == event.MouseMove:
+ if self._isDraging:
+ try:
+ self.emit(
+ QtCore.SIGNAL('draging(int, int)'),
+ event.globalPos().x(),
+ event.globalPos().y()
+ )
+ except Exception, d: # just in case
+ self.endDrag(canceled=True)
+ return True
+
+ return False
+
+#******************************************************************************
+#
+#******************************************************************************
+if __name__ == '__main__':
+ import sys
+
+ # test tool. Click on the green square to take it up, anpther click
+ # will release it again
+ class TestTool(DragTool):
+
+ def __init__(self, parent, px, releaseOnClick=True):
+ DragTool.__init__(self, parent, px, releaseOnClick=releaseOnClick)
+ self.connect(
+ self,
+ QtCore.SIGNAL('draging(int, int)'),
+ self.on_draging
+ )
+ self.connect(
+ self,
+ QtCore.SIGNAL('draged(int, int)'),
+ self.on_draged
+ )
+
+ def on_draging(self, x, y):
+ print "moving: %s, %s" % (x, y)
+
+ def on_draged(self, x, y):
+ print "dragged: %s, %s" % (x, y)
+
+
+
+ app = QtGui.QApplication(sys.argv)
+
+ w = QtGui.QWidget(None)
+ w.setGeometry(50, 50, 200, 200)
+
+ # setup pixmap
+ px = QtGui.QPixmap(QtCore.QSize(32, 32))
+ px.fill(QtCore.Qt.green)
+
+ # setup dragg tool and button
+ dragTool = TestTool(
+ w,
+ QtGui.QCursor(px, 0, 0),
+ #releaseOnClick=False
+ )
+ dragTool.setGeometry(-1, -1, 1, 12)
+
+ bt = QtGui.QToolButton(w)
+ bt.setIcon(QtGui.QIcon(px))
+ w.connect(
+ bt,
+ QtCore.SIGNAL('clicked()'),
+ dragTool.startDrag
+ )
+
+
+ w.show()
+ res = app.exec_()
+ sys.exit(res)
+
+
Added: trunk/fclient/fclient_lib/qt4ex/ctrls/editboxwrap.py
===================================================================
--- trunk/fclient/fclient_lib/qt4ex/ctrls/editboxwrap.py (rev 0)
+++ trunk/fclient/fclient_lib/qt4ex/ctrls/editboxwrap.py 2007-11-05 12:50:42 UTC (rev 47)
@@ -0,0 +1,201 @@
+"""Wrappers and tools for QLineEdit and QTextEdit"""
+
+from PyQt4 import QtCore, QtGui
+#***********************************************************************
+#
+#***********************************************************************
+class EditBoxContextMenuWrap(QtCore.QObject):
+ """Wrapper class for to handle custom context menus for editboxes
+
+ @note: make shure to always keep a reference to the wrapper
+ """
+
+ def __init__(self, edit):
+ """
+ @param edit: QTextEdit or QLineEdit
+ @attr edit: editbox
+ @attr actions: dict containing default actions for the editbox. Available actions are:
+ "Copy", "Cut", "Paste", "Redo", "SelectAll", "Undo". Caution: the dict is considered
+ to be read only!
+ @signal: 'customizeContextMenu(QObject* EditContextMenuWrap, QMenu* contextMenu)'.
+ This signal is emitted when the context menu for the editbox is requested. Use this signal
+ to customize the menu.
+ @signal: 'contextMenuActionSelected(QAction* action, bool isStandardAction)'.
+ Emitted when the user selected an action from the context menu. The flag indicates wether
+ a standard action or a custom action was selected
+
+ """
+ QtCore.QObject.__init__(self, edit)
+
+ self.edit = edit
+ self.actions = {}
+
+ edit.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.connect(
+ edit,
+ QtCore.SIGNAL('customContextMenuRequested(const QPoint &)'),
+ self.__call__
+ )
+
+ def __call__(self, pt):
+
+ # create custom LineEdit context menu
+ self.actions = {}
+ m = QtGui.QMenu(self.edit)
+
+ # add default QLineEdit actions
+ actions = (
+ (
+ 'Undo',
+ self.edit.trUtf8("&Undo"),
+ QtGui.QKeySequence.Undo,
+ self.trUtf8('Undos the last ation'),
+ self.edit.undo,
+ ),
+ (
+ 'Redo',
+ self.edit.trUtf8("&Redo"),
+ QtGui.QKeySequence.Redo,
+ self.trUtf8('Redos the last ation'),
+ self.edit.redo,
+ ),
+ (
+ 'Cut',
+ self.edit.trUtf8("Cu&t"),
+ QtGui.QKeySequence.Cut,
+ self.trUtf8('Cuts the currently selected text'),
+ self.edit.cut,
+ ),
+ (
+ 'Copy',
+ self.edit.trUtf8("&Copy"),
+ QtGui.QKeySequence.Copy,
+ self.trUtf8('Copies the currently selected text'),
+ self.edit.copy,
+ ),
+ (
+ 'Paste',
+ self.edit.trUtf8("&Paste"),
+ QtGui.QKeySequence.Paste,
+ self.trUtf8('Pastes text from the clipboard'),
+ self.edit.paste,
+ ),
+ (
+ 'SelectAll',
+ self.edit.trUtf8("Select All"),
+ QtGui.QKeySequence.SelectAll,
+ self.trUtf8('Selects all text'),
+ self.edit.selectAll,
+ ),
+ #TODO: Delete
+ #d->actions[QLineEditPrivate::ClearAct] = new QAction(QLineEdit::tr("Delete"), this);
+ #QObject::connect(d->actions[QLineEditPrivate::ClearAct], SIGNAL(triggered()), this, SLOT(_q_deleteSelected()));
+ )
+
+ # setup actions
+ for name, text, shortcut, tip, trigger in actions:
+ act = QtGui.QAction(text, None)
+ act.setObjectName(name)
+ act.setStatusTip(tip)
+ if shortcut.__class__.__name__ == 'StandardKey':
+ # BUG: in PyQt4.3 - already reported
+ # initializing QKeySequence() with a Qt.StandardKey gives segfault
+ act.setShortcut(shortcut)
+ else:
+ act.setShortcut(QtGui.QKeySequence(shortcut) )
+ self.connect(act, QtCore.SIGNAL('triggered()'), trigger)
+ m.addAction(act)
+ self.actions[name] = act
+
+ # adjust actions
+ # NOTE: disabling "SelectAll" is somewhat unnecessary, so leave it out
+ if isinstance(self.edit, QtGui.QLineEdit):
+ canPaste = not QtGui.QApplication.clipboard().text().isEmpty()
+ canRedo = self.edit.isRedoAvailable()
+ canUndo = self.edit.isUndoAvailable()
+ hasSelection = self.edit.hasSelectedText()
+ isReadOnly = self.edit.isReadOnly()
+ else:
+ canPaste = self.edit.canPaste()
+ canRedo = self.edit.document().isRedoAvailable()
+ canUndo = self.edit.document().isUndoAvailable()
+ hasSelection = self.edit.textCursor().hasSelection()
+ isReadOnly = self.edit.isReadOnly()
+
+ self.actions['Undo'].setEnabled(canUndo)
+ self.actions['Redo'].setEnabled(canRedo)
+ self.actions['Cut'].setEnabled(not isReadOnly and hasSelection)
+ self.actions['Copy'].setEnabled(hasSelection)
+ self.actions['Paste'].setEnabled(not isReadOnly and canPaste)
+ self.actions['SelectAll'].setEnabled(True)
+
+ # TODO: control chars submenu not yet implemented
+ # one problem is that one can not remove a menu from a menu
+ #~ ControlCharacters = (
+ #~ (QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRM Left-to-right mark"), 0x200e),
+ #~ (QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLM Right-to-left mark"), 0x200f),
+ #~ (QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWJ Zero width joiner"), 0x200d),
+ #~ (QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWNJ Zero width non-joiner"), 0x200c),
+ ...
[truncated message content] |
|
From: <jU...@us...> - 2007-11-05 12:43:06
|
Revision: 46
http://fclient.svn.sourceforge.net/fclient/?rev=46&view=rev
Author: jUrner
Date: 2007-11-05 04:43:07 -0800 (Mon, 05 Nov 2007)
Log Message:
-----------
error handling
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-05 12:42:27 UTC (rev 45)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-05 12:43:07 UTC (rev 46)
@@ -1057,9 +1057,13 @@
def pythonTime(self, fcpTime):
"""Converts a fcp time value to a python time value
@param fcpTime: (int, str) time to convert
+ @raise ValueError: if the fcpTime could not be converted
@return: (int) python time
"""
- fcpTime = int(fcpTime)
+ try:
+ fcpTime = int(fcpTime)
+ except Exception, d:
+ raise ValueError(d)
return fcpTime / 1000
########################################################
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-05 12:42:26
|
Revision: 45
http://fclient.svn.sourceforge.net/fclient/?rev=45&view=rev
Author: jUrner
Date: 2007-11-05 04:42:27 -0800 (Mon, 05 Nov 2007)
Log Message:
-----------
added refresh action an some fixes
Modified Paths:
--------------
trunk/fclient/fclient_widgets/peer_widget.py
Modified: trunk/fclient/fclient_widgets/peer_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/peer_widget.py 2007-11-04 17:33:33 UTC (rev 44)
+++ trunk/fclient/fclient_widgets/peer_widget.py 2007-11-05 12:42:27 UTC (rev 45)
@@ -152,16 +152,12 @@
if not observer in event:
event += observer
- self.clear()
- self._peers = {}
+ ##
+ self.listPeers()
- self._cfg.fcpClientManager.setPriority(
- self._fcpClient.connectionName(),
- pollPriority=self._cfg.fcpClientManager.priorities['Highest']
- )
- self._fcpClient.listPeers()
-
+
+
def handleClientDisconnected(self, event, params):
"""
"""
@@ -175,6 +171,7 @@
identity = params['identity']
item = self._peers.get(identity, None)
+
# add item if necessary
if item is None:
itemStrings = [
@@ -191,6 +188,13 @@
item.settext(self.HeaderIndexLastConnected, timeLastConnected)
item.setPeerData(params)
+ # hide item if necessary
+ flags = self.showPeers()
+ if params['opennet'] == self._fcpClient.FcpTrue:
+ item.setHidden(not flags & self.ShowPeersOpennet)
+ else:
+ item.setHidden(not flags & self.ShowPeersDarknet)
+
# get peer notes if possible
if params['opennet'] == self._fcpClient.FcpFalse:
self._requestsPending += 1
@@ -267,14 +271,26 @@
"""
result = ''
try:
- t = self._cfg.fcpClient.pythonTime(t)
- except: pass
+ t = self._fcpClient.pythonTime(t)
+ except ValueError: pass
else:
if t > 0:
result = numbers.format_time_delta(t, time.time())
return result
+ def listPeers(self):
+ self.clear()
+ self._peers = {}
+
+ self._cfg.fcpClientManager.setPriority(
+ self._fcpClient.connectionName(),
+ pollPriority=self._cfg.fcpClientManager.priorities['Highest']
+ )
+ self._fcpClient.listPeers()
+
+
+
def peers(self):
"""Returns all currently known peers
@return: (dict) identity --> treeItem
@@ -290,6 +306,10 @@
actions.append(ShowPeersMenu(menu, self))
+ act = RefreshAction('Refresh', self.trUtf8('Refresh'), menu, self)
+ menu.addAction(act)
+ actions.append(act)
+
return actions
@@ -308,7 +328,7 @@
self.setUpdatesEnabled(False)
for item in self.peers().values():
- if item.peerData()['opennet'] == self._cfg.fcpClient.FcpTrue:
+ if item.peerData()['opennet'] == self._fcpClient.FcpTrue:
item.setHidden(not flags & self.ShowPeersOpennet)
else:
item.setHidden(not flags & self.ShowPeersDarknet)
@@ -434,6 +454,35 @@
+class RefreshAction(QtGui.QAction):
+ """
+ """
+
+ def __init__(self, name, text, parent, tree):
+ """
+ """
+ QtGui.QAction.__init__(self, text, parent)
+ self.setObjectName(name)
+
+ #self.setShortcuts(
+ # [QtGui.QKeySequence(self.trUtf8('Ctrl+A'))]
+ # )
+ self.tree = tree
+ self.connect(
+ self,
+ QtCore.SIGNAL('triggered()'),
+ self.__call__
+ )
+
+
+ def __call__(self):
+ """
+ """
+ self.tree.listPeers()
+
+
+
+
'''########################################################
Sample Peer message
>> Peer
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-04 17:33:30
|
Revision: 44
http://fclient.svn.sourceforge.net/fclient/?rev=44&view=rev
Author: jUrner
Date: 2007-11-04 09:33:33 -0800 (Sun, 04 Nov 2007)
Log Message:
-----------
bit of renaming
Modified Paths:
--------------
trunk/fclient/fclient_ui/fcp_client_manager.py
trunk/fclient/fclient_widgets/peer_widget.py
Modified: trunk/fclient/fclient_ui/fcp_client_manager.py
===================================================================
--- trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-04 17:31:30 UTC (rev 43)
+++ trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-04 17:33:33 UTC (rev 44)
@@ -109,7 +109,7 @@
self.priorities = Priorities(self)
- def stopFcpClient(self, name): #TODO: name of client or client???
+ def stopClient(self, name): #TODO: name of client or client???
"""Stops a FcpClient
@name: (str) connection name of the client to stop
@note: the client is not closed in the call. To close the connection call FcpClient.close()
@@ -118,7 +118,7 @@
handler.stop()
- def newFcpClient(self,
+ def newClient(self,
name,
eventConnectedHandler,
connectPriority=None,
Modified: trunk/fclient/fclient_widgets/peer_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/peer_widget.py 2007-11-04 17:31:30 UTC (rev 43)
+++ trunk/fclient/fclient_widgets/peer_widget.py 2007-11-04 17:33:33 UTC (rev 44)
@@ -112,7 +112,7 @@
"""
if not self._isCreated:
self._isCreated = True
- self._fcpClient = self._cfg.fcpClientManager.newFcpClient('MeName', self.handleClientConnected)
+ self._fcpClient = self._cfg.fcpClientManager.newClient('MeName', self.handleClientConnected)
#############################################################
##
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-04 17:31:25
|
Revision: 43
http://fclient.svn.sourceforge.net/fclient/?rev=43&view=rev
Author: jUrner
Date: 2007-11-04 09:31:30 -0800 (Sun, 04 Nov 2007)
Log Message:
-----------
some more consts
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-04 17:29:50 UTC (rev 42)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-04 17:31:30 UTC (rev 43)
@@ -121,10 +121,20 @@
Version = '2.0'
FcpTrue = 'true'
FcpFalse = 'false'
+ class ConnectReason:
+ Connect = '1'
+ Reconnect = '2'
+
+
class DisconnectReason:
- """Reason for client disconnect"""
+ """Reasons for client disconnect
+ @cvar Shutdown: regular shutdown of the connection
+ @cvar SocketDied: connection to the node died unexpectingly
+ @cvar ConnectFailed: connection could not be established
+ """
Shutdown = '1'
SocketDied = '2'
+ ConnectFailed = '3'
class Events(events.Events):
@@ -757,10 +767,16 @@
timeElapsed += timeout
time.sleep(timeout)
+ sef.events.ClientDisconnect({'DisconnectReason': DisconnectReason.ConectFailed})
self._log.info(self.LogMessages.ConnectingFailed)
raise StopIteration
+
+
+ def connectionName(self):
+ """Returns the connection name the client uses"""
+ return self._connectionName
-
+
def handleMessage(self, msg):
"""Handles a message from the freenet node
@param msg: (Message) to handle
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-04 17:29:46
|
Revision: 42
http://fclient.svn.sourceforge.net/fclient/?rev=42&view=rev
Author: jUrner
Date: 2007-11-04 09:29:50 -0800 (Sun, 04 Nov 2007)
Log Message:
-----------
new: FcPClient manager
Modified Paths:
--------------
trunk/fclient/fclient_widgets/peer_widget.py
Modified: trunk/fclient/fclient_widgets/peer_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/peer_widget.py 2007-11-04 17:29:34 UTC (rev 41)
+++ trunk/fclient/fclient_widgets/peer_widget.py 2007-11-04 17:29:50 UTC (rev 42)
@@ -1,8 +1,4 @@
"""Sketch for a widget handling peer nodes
-
-Note: super slow currently. Have to experiment with multiple clients
-to enable playing around with poll timeouts for individual clients
-
"""
import os, sys
@@ -54,6 +50,8 @@
#***************************************************************************************************
#
#***************************************************************************************************
+#TODO: user should not be alowed to show / hide peers while we are listing them
+
class PeerWidget(QtGui.QTreeWidget):
"""
"""
@@ -76,6 +74,7 @@
self._fcpEvents = None
self._isCreated = False
self._peers = {} # identity --> item
+ self._requestsPending = 0
self._showPeersFlags = self.ShowPeersAll
QtGui.QWidget.__init__(self, parent)
@@ -113,7 +112,7 @@
"""
if not self._isCreated:
self._isCreated = True
- self._cfg.connectFcpNode(self.handleClientConnected)
+ self._fcpClient = self._cfg.fcpClientManager.newFcpClient('MeName', self.handleClientConnected)
#############################################################
##
@@ -137,18 +136,17 @@
"""
"""
self._fcpEvents = (
- (self._cfg.fcpClient.events.ClientDisconnected, self.handleClientDisconnected),
+ (self._fcpClient.events.ClientDisconnected, self.handleClientDisconnected),
- (self._cfg.fcpClient.events.Peer, self.handlePeer),
- (self._cfg.fcpClient.events.EndListPeers, self.handleEndListPeers),
- (self._cfg.fcpClient.events.PeerRemoved, self.handlePeerRemoved),
- (self._cfg.fcpClient.events.UnknownNodeIdentifier, self.handleUnknownNodeIdentifier),
+ (self._fcpClient.events.Peer, self.handlePeer),
+ (self._fcpClient.events.EndListPeers, self.handleEndListPeers),
+ (self._fcpClient.events.PeerRemoved, self.handlePeerRemoved),
+ (self._fcpClient.events.UnknownNodeIdentifier, self.handleUnknownNodeIdentifier),
- (self._cfg.fcpClient.events.PeerNote, self.handlePeerNote),
- (self._cfg.fcpClient.events.EndListPeerNotes, self.handleEndListPeerNotes),
+ (self._fcpClient.events.PeerNote, self.handlePeerNote),
+ (self._fcpClient.events.EndListPeerNotes, self.handleEndListPeerNotes),
)
-
# take care to not connect twice
for event, observer in self._fcpEvents:
if not observer in event:
@@ -156,8 +154,13 @@
self.clear()
self._peers = {}
- self._cfg.fcpClient.listPeers()
+ self._cfg.fcpClientManager.setPriority(
+ self._fcpClient.connectionName(),
+ pollPriority=self._cfg.fcpClientManager.priorities['Highest']
+ )
+ self._fcpClient.listPeers()
+
def handleClientDisconnected(self, event, params):
"""
@@ -189,14 +192,20 @@
item.setPeerData(params)
# get peer notes if possible
- if params['opennet'] == self._cfg.fcpClient.FcpFalse:
- self._cfg.fcpClient.listPeerNotes(identity)
-
-
+ if params['opennet'] == self._fcpClient.FcpFalse:
+ self._requestsPending += 1
+ self._fcpClient.listPeerNotes(identity)
+
def handleEndListPeers(self, event, params):
"""
"""
+ if self._requestsPending <= 0:
+ self._cfg.fcpClientManager.setPriority(
+ self._fcpClient.connectionName(),
+ pollPriority=self._cfg.fcpClientManager.priorities['Lowest']
+ )
+
def handlePeerRemoved(self, peer):
@@ -218,15 +227,22 @@
#TODO: shouldn' t happen
return
- if params['PeerNoteType'] == self._cfg.fcpClient.PeerNoteType.Private:
+ if params['PeerNoteType'] == self._fcpClient.PeerNoteType.Private:
item.setText(self.HeaderIndexNotes, params['NoteText'])
def handleEndListPeerNotes(self, event, params):
"""
"""
+ self._requestsPending -= 1
+ if self._requestsPending <= 0:
+ self._requestsPending = 0
+ self._cfg.fcpClientManager.setPriority(
+ self._fcpClient.connectionName(),
+ pollPriority=self._cfg.fcpClientManager.priorities['Lowest']
+ )
+
-
#######################################################
##
## methods
@@ -240,6 +256,8 @@
for event, observer in self._fcpEvents:
if not observer in event:
event -= observer
+
+ self._cfg.fcpClientHandler.closeClient(self._fcpClient.connectionName())
def formatTimeDelta(self, t):
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-04 17:29:34
|
Revision: 41
http://fclient.svn.sourceforge.net/fclient/?rev=41&view=rev
Author: jUrner
Date: 2007-11-04 09:29:34 -0800 (Sun, 04 Nov 2007)
Log Message:
-----------
new: FcPClient manager
Modified Paths:
--------------
trunk/fclient/config.py
Modified: trunk/fclient/config.py
===================================================================
--- trunk/fclient/config.py 2007-11-04 17:28:46 UTC (rev 40)
+++ trunk/fclient/config.py 2007-11-04 17:29:34 UTC (rev 41)
@@ -7,7 +7,9 @@
from PyQt4 import QtCore
from fclient_lib.qt4ex import assistant, language, settingsbase, resources
+from fclient_ui.fcp_client_manager import FcpClientManager
+
_ = os.path.join
#***********************************************************************
#
@@ -34,7 +36,7 @@
#*****************************************************************************
class Config(object):
- def __init__(self, parent, connectDelay=100, pollDelay=300):
+ def __init__(self, parent):
"""
@param parent: parent widget
@param connectDelay: (miliseconds) frequency by qich a connection to the node is made
@@ -42,95 +44,32 @@
value will make messages from the node come in quickly but make the gui unresponsive
to user interaction
- @ivar assistant: app global user docs management
+ @ivar assistant: app global user documentation management
@ivar defaultDownloadsDir: default directory for downloads
- @ivar fcpClient: app global FcpClient
+ @ivar fcpClientManager: app global FcpClientManager
@ivar language: app global language management
@ivar nodeHello: NodeHello message as returned from the client as soon as a connection
is established
@ivar resources: app global resource management
- @ivar settings: global settings
+ @ivar settings: app global settings
"""
#TODO: assistant can not handle locale dependend docs yet
self.assistant = assistant.Assistant(parent, profile=AssistantProfile, pages=DocPages)
self.defaultDownloadsDir = os.path.join(BaseDir, 'downloads')
-
+ self.fcpClientManager = FcpClientManager(parent)
self.language = language.Language([LanguageDir, ], translationPrefixes=TranslationPrefixes)
-
self.resources = resources.Resources([ResourceDir, ], )
self.settings = settingsbase.Settings(OrgName, AppName)
- self.fcpClient = None
- self.nodeHello = None
- self.connectDelay = connectDelay
- self.pollDelay = pollDelay
- self._pollTimer = QtCore.QTimer()
- self._connectMethod = None
- self._connectTimer = QtCore.QTimer()
- QtCore.QObject.connect(
- self._connectTimer,
- QtCore.SIGNAL('timeout()'),
- self.handleFcpConnect
- )
-
- #TODO: have to find a way to handle multiple clients
- def connectFcpNode(self, eventConnectedHandler):
- """Establishes a connection to the freenet node
- @param eventConnectedHandler: handler to handle the events.ClientConnected of the client
- @note: the clients events.ClientConnected gets automatically connected in the call. So no
- need to connect once more.
- @note: make shure not to access the 'fcpClient' or 'nodeHello' attrs of the config before
- the handler gets called.
- """
- if self.fcpClient is None:
- # some magic here to determine the client version to use
- from fclient_lib.fcp.fcp2_0 import FcpClient
- self.fcpClient = FcpClient(
- connectionName='',
- #verbosity=FcpClient.Verbosity.Debug,
- )
- self.fcpClient.events.ClientDisconnected += self.handleFcpClientDisconnected
- QtCore.QObject.connect(
- self._pollTimer,
- QtCore.SIGNAL('timeout()'),
- self.fcpClient.next
- )
- self._connectMethod = self.fcpClient.connect()
- self.fcpClient.events.ClientConnected += eventConnectedHandler
- self._connectTimer.start(self.connectDelay)
-
- else:
- cb(self.fcpClient.events.ClientConnected, self.nodeHello)
- def handleFcpConnect(self):
- """Handles establishing of the node connection"""
- self._connectTimer.stop()
- try:
- result = self._connectMethod.next()
- if result is not None:
- self.nodeHello = result
- self._pollTimer.start(self.pollDelay)
-
- except StopIteration:
- return
- self._connectTimer.start()
-
-
- def handleFcpClientDisconnected(self, params):
- """Handles client disconneting"""
- self.fcpClient = None
- self.nodeHello = None
-
-
-
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-04 17:28:44
|
Revision: 40
http://fclient.svn.sourceforge.net/fclient/?rev=40&view=rev
Author: jUrner
Date: 2007-11-04 09:28:46 -0800 (Sun, 04 Nov 2007)
Log Message:
-----------
added support to handle multiple clients
Added Paths:
-----------
trunk/fclient/fclient_ui/
trunk/fclient/fclient_ui/__init__.py
trunk/fclient/fclient_ui/fcp_client_manager.py
Added: trunk/fclient/fclient_ui/__init__.py
===================================================================
--- trunk/fclient/fclient_ui/__init__.py (rev 0)
+++ trunk/fclient/fclient_ui/__init__.py 2007-11-04 17:28:46 UTC (rev 40)
@@ -0,0 +1 @@
+
Added: trunk/fclient/fclient_ui/fcp_client_manager.py
===================================================================
--- trunk/fclient/fclient_ui/fcp_client_manager.py (rev 0)
+++ trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-04 17:28:46 UTC (rev 40)
@@ -0,0 +1,299 @@
+
+
+from PyQt4 import QtCore
+#**************************************************************************************
+#
+#**************************************************************************************
+class Priority(object):
+ """Priority"""
+ __slots__ = ('name', 'value')
+
+
+ def __init__(self, name, value):
+ """
+ @param name: (str) name of the priority
+ @param value: (int) value of the priority
+ """
+ self.name = name
+ self.value = value
+ def __int__(self): return self.value
+ def __long__(self): return self.__int__()
+
+#**************************************************************************************
+#
+#**************************************************************************************
+class Priorities(QtCore.QObject):
+ """Class handling poll priorities
+
+ >>> p = Priorities(None)
+ >>> priority = p['Highest']
+ >>> priority.name
+ 'Highest'
+ >>> int(priority)
+ 0
+
+ >>> p['Highest'] = 33
+ >>> int(priority)
+ 33
+
+ >>> for translatedName, priority in p.listPriorityNames(): priority.name
+ 'Highest'
+ 'High'
+ 'Normal'
+ 'Low'
+ 'Lowest'
+ """
+
+ # cheat a bit to get dynamic translation working
+ def trUtf8(self, what): return what
+
+
+ def __init__(self, parent):
+ """
+ @param parent: (QObject) parent or None
+ @note: supported priorities are: 'Highest', 'High', 'Normal', 'Low', 'Lowest'
+ """
+ QtCore.QObject.__init__(self, parent)
+
+ self._values = (
+ Priority(self.trUtf8('Highest'), 0),
+ Priority(self.trUtf8('High'), 100),
+ Priority(self.trUtf8('Normal'), 300),
+ Priority(self.trUtf8('Low'), 600),
+ Priority(self.trUtf8('Lowest'), 1000),
+ )
+ self._valuesDict = {}
+ for priority in self._values:
+ self._valuesDict[priority.name] = priority
+
+
+ def __getitem__(self, name):
+ """Returns the a priority given its name"""
+ return self._valuesDict[name]
+
+
+ def __setitem__(self, name, value):
+ """Sets the value of a priority
+ @param name: (str) name of the priority
+ @param value: (int) value in milisecomds
+ """
+ self._valuesDict[name].value = value
+
+
+ def listPriorityNames(self):
+ """Returns a list containing all priorities
+ @return: (list) [(QString) translatedName, (Priority) priority]
+ """
+ out = []
+ for priority in self._values:
+ out.append( (QtCore.QObject.trUtf8(self, priority.name), priority) )
+ return out
+
+#*****************************************************************************
+#
+#*****************************************************************************
+class FcpClientManager(QtCore.QObject):
+ """Manager to handle multiple Fcp clients
+ """
+
+
+ def __init__(self, parent):
+ """
+ @param parent: (QObject) parent or None
+ @ivar fcpClients: dict mapping connection names to FcpClients
+ @ivar priorities: L{Priorities} the manager supports
+ """
+ QtCore.QObject.__init__(self, parent)
+
+ self.fcpClients = {}
+ self.priorities = Priorities(self)
+
+
+ def stopFcpClient(self, name): #TODO: name of client or client???
+ """Stops a FcpClient
+ @name: (str) connection name of the client to stop
+ @note: the client is not closed in the call. To close the connection call FcpClient.close()
+ """
+ handler = self.fcpClients.pop(name)
+ handler.stop()
+
+
+ def newFcpClient(self,
+ name,
+ eventConnectedHandler,
+ connectPriority=None,
+ pollPriority=None,
+ ):
+ """Creates a new Fcp client
+ @param name: (str) unique connection name
+ @param eventConnectedHandler: method that handles FcpClient.EventConnected
+ @param connectPriority: (L{Priority}) priority when establishing node connection
+ @param pollPriority: (L{Priority}) runtime priority
+ @return: (FcpClient) instance
+
+ @note: the eventConnectedHandler handler passed is automatically connected
+ to FcpClient.EventConnected. No need to connect to receive events.
+ """
+ if name in self.fcpClients:
+ raise ValueError('Fcp client already exists: %r' % name)
+
+ # some magic here to determine the client version to use
+ from fclient_lib.fcp.fcp2_0 import FcpClient
+ fcpClient = FcpClient(
+ connectionName=name,
+ #verbosity=FcpClient.Verbosity.Debug,
+ )
+ fcpClient.events.ClientConnected += eventConnectedHandler
+ handler = FcpClientHandler(
+ fcpClient,
+ self.handleFcpClientDisconnected,
+ connectPriority if connectPriority is not None else self.priorities['Normal'],
+ pollPriority if pollPriority is not None else self.priorities['Normal'],
+ )
+ self.fcpClients[name] = handler
+ fcpClient.events.ClientDisconnected += handler.handleFcpClientDisconnected
+ handler.connect()
+ return fcpClient
+
+
+ def setPriority(self, name, connectPriority=None, pollPriority=None):
+ """Sets priorities of a FcpClient
+ @param connectPriority: (L{Priority}) priority when establishing node connection
+ @param pollPriority: (L{Priority}) runtime priority
+ """
+ handler = self.fcpClients[name]
+ if connectPriority is not None:
+ handler.setConnectPriority(connectPriority)
+ if pollPriority is not None:
+ handler.setPollPriority(pollPriority)
+
+
+ def handleFcpClientDisconnected(self, handler, params):
+ """Handles FcpClient.EventDisconnect"""
+
+ disconnectReason = params['DisconnectReason']
+ fcpClient = handler.fcpClient
+
+ # see how to handle the cases
+ if disconnectReason == fcpClient.DisconnectReason.Shutdown:
+ handler.stop()
+
+ elif disconnectReason == fcpClient.DisconnectReason.SocketDied:
+ # 1. wait some time, cos more alerts may come in
+ # 2. inform user
+ # 3. reconnect if desired
+ pass
+
+ elif disconnectReason == fcpClient.DisconnectReason.ConnectFailed:
+ # 1. wait some time, cos more alerts may come in
+ # 2. inform user
+ # 3. reconnect if desired
+ pass
+
+ else:
+ raise ValueError('Unknown disconnect reason: %r' % disconnectReason)
+
+
+
+#**************************************************************************************
+#
+#**************************************************************************************
+class FcpClientHandler(object):
+ """Handles one single FcpClient
+ """
+
+
+ def __init__(self, fcpClient, handleFcpClientDisconnected, connectPriority, pollPriority):
+ """
+ @param fcpClient: FcpClient to handle
+ @param handleFcpClientDisconnected: handler to call when the client disconnects
+ @param connectPriority: (L{Priority}) priority when establishing node connection
+ @param pollPriority: (L{Priority}) runtime priority
+ """
+ self.fcpClient = fcpClient
+ self.handleFcpClientDisconnected = handleFcpClientDisconnected
+ self.connectMethod = fcpClient.connect()
+
+ self.connectPriority = connectPriority
+ self.pollPriority = pollPriority
+
+ self.connectTimer = QtCore.QTimer()
+ QtCore.QObject.connect(
+ self.connectTimer,
+ QtCore.SIGNAL('timeout()'),
+ self.handleConnect
+ )
+
+ self.pollTimer = QtCore.QTimer()
+ QtCore.QObject.connect(
+ self.pollTimer,
+ QtCore.SIGNAL('timeout()'),
+ self.fcpClient.next
+ )
+
+
+ def connect(self):
+ """Establishes the connection from the client to the node"""
+ self.stop()
+ self.connectTimer.start(int(self.connectPriority))
+
+
+ def start(self):
+ """Starts polling the FcpClient"""
+ self.pollTimer.start(int(self.pollPriority))
+
+
+ def stop(self):
+ """Stops polling the FcpClient"""
+ self.connectTimer.stop()
+ self.pollTimer.stop()
+
+
+ def setPollPriority(self, priority):
+ """Adjusts the poll priority of the FcpClient
+ @param priority: L{Priority}
+ """
+ isActive = self.pollTimer.isActive()
+ self.pollTimer.stop()
+ if isActive:
+ self.pollTimer.start(int(priority))
+ self.pollPriority = priority
+
+
+ def setConnectPriority(self, priority):
+ """Adjusts the priority wich is used to establish a connection to the node
+ @param priority: L{Priority}
+ """
+ isActive = self.connectTimer.isActive()
+ self.connectTimer.stop()
+ if isActive:
+ self.connectTimer.start(int(priority))
+ self.connectPriority = priority
+
+
+ def handleFcpClientDisconnected(self, params):
+ """Handles FcpClient.EventDisconnect"""
+ self.stop()
+ self.handleFcpClientDisconnected(self, params)
+
+
+ def handleConnect(self):
+ """Handles establishing of the node connection"""
+ self.connectTimer.stop()
+ try:
+ result = self.connectMethod.next()
+ if result is not None:
+ self.nodeHello = result
+ self.pollTimer.start(int(self.pollPriority))
+ return
+ except StopIteration:
+ return
+ self.connectTimer.start(int(self.connectPriority))
+
+#******************************************************************************************
+#
+#******************************************************************************************
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-02 16:02:47
|
Revision: 39
http://fclient.svn.sourceforge.net/fclient/?rev=39&view=rev
Author: jUrner
Date: 2007-11-02 09:02:52 -0700 (Fri, 02 Nov 2007)
Log Message:
-----------
notes
Modified Paths:
--------------
trunk/fclient/config.py
Modified: trunk/fclient/config.py
===================================================================
--- trunk/fclient/config.py 2007-11-02 16:02:28 UTC (rev 38)
+++ trunk/fclient/config.py 2007-11-02 16:02:52 UTC (rev 39)
@@ -80,6 +80,7 @@
)
+ #TODO: have to find a way to handle multiple clients
def connectFcpNode(self, eventConnectedHandler):
"""Establishes a connection to the freenet node
@param eventConnectedHandler: handler to handle the events.ClientConnected of the client
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-02 16:02:27
|
Revision: 38
http://fclient.svn.sourceforge.net/fclient/?rev=38&view=rev
Author: jUrner
Date: 2007-11-02 09:02:28 -0700 (Fri, 02 Nov 2007)
Log Message:
-----------
added peer notes handling
Modified Paths:
--------------
trunk/fclient/fclient_widgets/peer_widget.py
Modified: trunk/fclient/fclient_widgets/peer_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/peer_widget.py 2007-11-02 16:01:29 UTC (rev 37)
+++ trunk/fclient/fclient_widgets/peer_widget.py 2007-11-02 16:02:28 UTC (rev 38)
@@ -1,5 +1,8 @@
"""Sketch for a widget handling peer nodes
+Note: super slow currently. Have to experiment with multiple clients
+to enable playing around with poll timeouts for individual clients
+
"""
import os, sys
@@ -14,7 +17,6 @@
import config
-from fclient_lib import fcp
from fclient_lib.pyex import numbers
@@ -28,6 +30,30 @@
#***************************************************************************************************
#
#***************************************************************************************************
+class PeerItem(QtGui.QTreeWidgetItem):
+ """
+ """
+
+ def __init__(self, *args):
+ """
+ """
+ QtGui.QTreeWidgetItem.__init__(self, *args)
+
+ self._peerData = None
+
+ def peerData(self):
+ """
+ """
+ return self._peerData
+
+ def setPeerData(self, peerData):
+ """
+ """
+ self._peerData = peerData
+
+#***************************************************************************************************
+#
+#***************************************************************************************************
class PeerWidget(QtGui.QTreeWidget):
"""
"""
@@ -117,6 +143,10 @@
(self._cfg.fcpClient.events.EndListPeers, self.handleEndListPeers),
(self._cfg.fcpClient.events.PeerRemoved, self.handlePeerRemoved),
(self._cfg.fcpClient.events.UnknownNodeIdentifier, self.handleUnknownNodeIdentifier),
+
+ (self._cfg.fcpClient.events.PeerNote, self.handlePeerNote),
+ (self._cfg.fcpClient.events.EndListPeerNotes, self.handleEndListPeerNotes),
+
)
# take care to not connect twice
@@ -150,20 +180,24 @@
timeLastConnected,
''
]
- item = QtGui.QTreeWidgetItem(self, itemStrings)
- item.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(identity))
- self._peers[identity] = (item, params)
+ item = PeerItem(self, itemStrings)
+ self._peers[identity] = item
# update item
else:
item.settext(self.HeaderIndexLastConnected, timeLastConnected)
- self._peers[identity] = (item, params)
+ item.setPeerData(params)
+
+ # get peer notes if possible
+ if params['opennet'] == self._cfg.fcpClient.FcpFalse:
+ self._cfg.fcpClient.listPeerNotes(identity)
+
def handleEndListPeers(self, event, params):
"""
"""
-
+
def handlePeerRemoved(self, peer):
"""
@@ -174,6 +208,25 @@
"""
"""
+
+ def handlePeerNote(self, event, params):
+ """
+ """
+ identity = params['NodeIdentifier']
+ item = self._peers.get(identity, None)
+ if item is None:
+ #TODO: shouldn' t happen
+ return
+
+ if params['PeerNoteType'] == self._cfg.fcpClient.PeerNoteType.Private:
+ item.setText(self.HeaderIndexNotes, params['NoteText'])
+
+
+ def handleEndListPeerNotes(self, event, params):
+ """
+ """
+
+
#######################################################
##
## methods
@@ -204,18 +257,9 @@
return result
- def identityFromItem(self, item):
- """Returns the peer identity an item is associated to
- @param item: treeItem
- @return: (str) peer identity
- """
- v = item.data(0, QtCore.Qt.UserRole)
- return str(v.toString())
-
-
def peers(self):
"""Returns all currently known peers
- @return: (dict) identity --> (treeItem, peer)
+ @return: (dict) identity --> treeItem
"""
return self._peers
@@ -245,8 +289,8 @@
"""
self.setUpdatesEnabled(False)
- for item, peer in self.peers().values():
- if peer['opennet'] == self._cfg.fcpClient.FcpTrue:
+ for item in self.peers().values():
+ if item.peerData()['opennet'] == self._cfg.fcpClient.FcpTrue:
item.setHidden(not flags & self.ShowPeersOpennet)
else:
item.setHidden(not flags & self.ShowPeersDarknet)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|