def __init__(self, parent=None, settings_path=""): super(ScudCloud, self).__init__(parent) self.setWindowTitle('ScudCloud') self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id( "scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) self.stackedWidget = QtGui.QStackedWidget() centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.startURL = Resources.SIGNIN_URL if self.identifier is not None: self.startURL = self.domain() self.addWrapper(self.startURL) self.addMenu() self.tray = Systray(self) self.systray(ScudCloud.minimized) self.installEventFilter(self) self.statusBar().showMessage('Loading Slack...')
def __init__(self, parent = None, settings_path = ""): super(ScudCloud, self).__init__(parent) self.setWindowTitle('ScudCloud') self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) self.stackedWidget = QtGui.QStackedWidget() self.stackedWidget.addWidget(webView) centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.addMenu() self.tray = Systray(self) self.systray(ScudCloud.minimized) self.installEventFilter(self) if self.identifier is None: webView.load(QtCore.QUrl(Resources.SIGNIN_URL)) else: webView.load(QtCore.QUrl(self.domain())) webView.show()
def __init__(self, parent = None, settings_path = ""): super(zcswebapp, self).__init__(parent) self.setWindowTitle('zcswebapp') self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('zcswebapp.png')) self.settings = QSettings(self.settings_path + '/zcswebapp.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id("zcswebapp.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) self.stackedWidget = QtGui.QStackedWidget() self.stackedWidget.addWidget(webView) centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.addMenu() self.tray = Systray(self) self.systray(zcswebapp.minimized) self.installEventFilter(self) if self.identifier is None: webView.load(QtCore.QUrl(Resources.SIGNIN_URL)) else: webView.load(QtCore.QUrl(self.domain())) webView.show()
def __init__(self, parent = None, settings_path = ""): super(ScudCloud, self).__init__(parent) self.setWindowTitle('ScudCloud') self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) self.stackedWidget = QtGui.QStackedWidget() centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.startURL = Resources.SIGNIN_URL if self.identifier is not None: self.startURL = self.domain() self.addWrapper(self.startURL) self.addMenu() self.tray = Systray(self) self.systray(ScudCloud.minimized) self.installEventFilter(self) self.statusBar().showMessage('Loading Slack...')
def run(self): self._logger.info("Starting DanDrop v" + self._globals.version + "...") # Create and start the API server if self._config.enableApi: self._api_server = ApiServer(self, self._config.tcpListenIp, self._config.tcpListenPort) # Start the web server if self._config.enableWebServer: self._web_server = WebServer() # Check if the systray should be shown #if self._config.enableSystray: if True: self._logger.debug("Creating systray...") self._systray = Systray(self._globals) # Create the file watcher self._fswatcher = FSWatcher(self._configOld) self._fswatchers = [] for share in self._config.shares: print share.sync_folder # Start watching and syncing files self._fswatcher.watch()
def __init__(self, parent=None): super(ScudCloud, self).__init__(parent) self.setWindowTitle('ScudCloud') self.notifier = Notifier(self.APP_NAME, get_resource_path('scudcloud.png')) self.settings = QSettings(expanduser("~")+"/.scudcloud", QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.leftPane = LeftPane(self) self.cookiesjar = PersistentCookieJar(self) webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) self.stackedWidget = QtGui.QStackedWidget() self.stackedWidget.addWidget(webView) centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.addMenu() self.tray = Systray(self) self.systray() self.installEventFilter(self) self.zoom() if self.identifier is None: webView.load(QtCore.QUrl(self.SIGNIN_URL)) else: webView.load(QtCore.QUrl(self.domain())) webView.show()
def _setupUI(self): self.setWindowTitle(self.NAME) self._addMenu() self._addToolbar() self._tabs = QtGui.QTabWidget() self._tabs.setTabsClosable(True) self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused) self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose) self._editor = QtGui.QPlainTextEdit() self._editor.setFixedHeight(self._editor.fontMetrics().height() * 2) self._editor.installEventFilter(SuggesterKeyPressEventFilter(self, Suggester(self._editor))) speakButton = QtGui.QPushButton(self._("&Send")) self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak) grid = QtGui.QGridLayout() grid.setRowStretch(0, 1) grid.addWidget(self._tabs, 0, 0, 1, -1) grid.addWidget(self._editor, 1, 0) grid.addWidget(speakButton, 1, 1) widget = QtGui.QWidget() widget.setLayout(grid) self.setCentralWidget(widget) tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self) self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused) widget.installEventFilter(tabWidgetFocusEventFilter) self.centralWidget().hide() size = self.getSetting("window", "size") if not size: size = QtCore.QSize(640, 480) self.resize(size) position = self.getSetting("window", "position") if not position: screen = QtGui.QDesktopWidget().screenGeometry() position = QtCore.QPoint((screen.width()-size.width())/2, (screen.height()-size.height())/2) self.move(position) self._updateLayout() menu = QtGui.QMenu(self) menu.addAction(self._menus["file"]["connect"]) menu.addAction(self._menus["file"]["disconnect"]) menu.addSeparator() menu.addAction(self._menus["file"]["exit"]) self._trayIcon = Systray(self._icon, self) self._trayIcon.setContextMenu(menu) self._trayIcon.setToolTip(self.DESCRIPTION)
def __init__(self, parent=None, settings_path=""): super(ScudCloud, self).__init__(parent) self.setWindowTitle('ScudCloud') self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id( "scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) self.stackedWidget = QtGui.QStackedWidget() centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) startURL = Resources.SIGNIN_URL if self.identifier is not None: startURL = self.domain() self.addWrapper(startURL) self.addMenu() self.tray = Systray(self) self.systray(ScudCloud.minimized) self.installEventFilter(self) self.statusBar().showMessage('Loading Slack...') # Starting unread msgs counter self.setupTimer() # Watch for suspend/resume events if DBusQtMainLoop is not None: DBusQtMainLoop(set_as_default=True) dbus.SystemBus().add_signal_receiver( self.sleep, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1')
def __init__(self, *args): super().__init__(*args) import main from Settings import SettingsAccessor, DEFAULT_SETTINGS from xwaredpy import XwaredPy from etmpy import EtmPy from systray import Systray import mounts from Notify import Notifier from frontendpy import FrontendPy from Schedule import Scheduler logging.info("XWARE DESKTOP STARTS") self.setApplicationName("XwareDesktop") self.setApplicationVersion(__version__) os.chdir(os.path.dirname(os.path.abspath(__file__))) self.checkOneInstance() self.settings = SettingsAccessor(self, configFilePath=constants.CONFIG_FILE, defaultDict=DEFAULT_SETTINGS) # components self.xwaredpy = XwaredPy(self) self.etmpy = EtmPy(self) self.mountsFaker = mounts.MountsFaker() self.dbusNotify = Notifier(self) self.frontendpy = FrontendPy(self) self.scheduler = Scheduler(self) self.settings.applySettings.connect(self.slotCreateCloseMonitorWindow) self.mainWin = main.MainWindow(None) self.mainWin.show() self.sigMainWinLoaded.emit() self.systray = Systray(self) self.settings.applySettings.emit() if self.settings.get("internal", "previousversion") == "0.8": # upgraded or fresh installed from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices QDesktopServices.openUrl( QUrl("https://github.com/Xinkai/XwareDesktop/wiki/使用说明")) self.settings.set("internal", "previousversion", __version__)
def __init__(self, *args): super().__init__(*args) import main from Settings import SettingsAccessor, DEFAULT_SETTINGS from xwaredpy import XwaredPy from etmpy import EtmPy from systray import Systray import mounts from Notify import Notifier from frontendpy import FrontendPy from Schedule import Scheduler logging.info("XWARE DESKTOP STARTS") self.setApplicationName("XwareDesktop") self.setApplicationVersion(__version__) self.checkUsergroup() os.chdir(os.path.dirname(os.path.abspath(__file__))) self.checkOneInstance() self.settings = SettingsAccessor(self, configFilePath=constants.CONFIG_FILE, defaultDict=DEFAULT_SETTINGS) # components self.xwaredpy = XwaredPy(self) self.etmpy = EtmPy(self) self.mountsFaker = mounts.MountsFaker() self.dbusNotify = Notifier(self) self.frontendpy = FrontendPy(self) self.scheduler = Scheduler(self) self.settings.applySettings.connect(self.slotCreateCloseMonitorWindow) self.mainWin = main.MainWindow(None) self.mainWin.show() self.sigMainWinLoaded.emit() self.systray = Systray(self) self.settings.applySettings.emit()
def __init__(self, parent = None, settings_path = ""): super(ScudCloud, self).__init__(parent) self.setWindowTitle('ScudCloud') self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) self.stackedWidget = QtGui.QStackedWidget() centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) startURL = Resources.SIGNIN_URL if self.identifier is not None: startURL = self.domain() self.addWrapper(startURL) self.addMenu() self.tray = Systray(self) self.systray(ScudCloud.minimized) self.installEventFilter(self) self.statusBar().showMessage('Loading Slack...') # Starting unread msgs counter self.setupTimer() # Watch for suspend/resume events if DBusQtMainLoop is not None: DBusQtMainLoop(set_as_default=True) dbus.SystemBus().add_signal_receiver(self.sleep, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1')
class Snakefire(object): DOMAIN = "snakefire.org" NAME = "Snakefire" DESCRIPTION = "Snakefire: Campfire Linux Client" VERSION = "1.0.1" ICON = "snakefire.png" COLORS = { "time": "c0c0c0", "alert": "ff0000", "join": "cb81cb", "leave": "cb81cb", "topic": "808080", "upload": "000000", "message": "000000", "nick": "808080", "nickAlert": "ff0000", "nickSelf": "000080", "tabs": { "normal": None, "new": QtGui.QColor(0, 0, 255), "alert": QtGui.QColor(255, 0, 0) } } def __init__(self): self.DESCRIPTION = self._(self.DESCRIPTION) self._worker = None self._settings = {} self._canConnect = False self._cfDisconnected() self._qsettings = QtCore.QSettings() self._icon = QtGui.QIcon(":/icons/%s" % (self.ICON)) self.setWindowIcon(self._icon) self.setAcceptDrops(True) self._setupUI() settings = self.getSettings("connection") self._canConnect = False if settings["subdomain"] and settings["user"] and settings["password"]: self._canConnect = True self._updateLayout() if settings["connect"]: self.connectNow() def _(self, string, module=None): return str(QtCore.QCoreApplication.translate(module or Snakefire.NAME, string)) def showEvent(self, event): if self._trayIcon.isVisible(): if self._trayIcon.isAlerting(): self._trayIcon.stopAlert() return self._trayIcon.show() def dragEnterEvent(self, event): room = self.getCurrentRoom() canUpload = not self._rooms[room.id]["upload"] if room else False if canUpload and self._getDropFile(event): event.acceptProposedAction() def dropEvent(self, event): room = self.getCurrentRoom() path = self._getDropFile(event) if room and path: self._upload(room, path) def _getDropFile(self, event): files = [] urls = event.mimeData().urls() if urls: for url in urls: path = url.path() if path and os.path.exists(path) and os.path.isfile(path): try: handle = open(str(path)) handle.close() files.append(str(path)) except Exception as e: pass if len(files) > 1: files = [] return files[0] if files else None def getSetting(self, group, setting): settings = self.getSettings(group, asString=False); return settings[setting] if setting in settings else None def setSetting(self, group, setting, value): self._qsettings.beginGroup(group); self._qsettings.setValue(setting, value) self._qsettings.endGroup(); def getSettings(self, group, asString=True, reload=False): defaults = { "connection": { "subdomain": None, "user": None, "password": None, "ssl": False, "connect": False, "join": False, "rooms": [] }, "program": { "minimize": False } } if reload or not group in self._settings: settings = defaults[group] if group in defaults else {} self._qsettings.beginGroup(group); for setting in self._qsettings.childKeys(): settings[str(setting)] = self._qsettings.value(setting).toPyObject() self._qsettings.endGroup(); boolSettings = [] if group == "connection": boolSettings += ["ssl", "connect", "join"] elif group == "program": boolSettings += ["minimize"] for boolSetting in boolSettings: try: settings[boolSetting] = True if ["true", "1"].index(str(settings[boolSetting]).lower()) >= 0 else False except: settings[boolSetting] = False if group == "connection" and settings["subdomain"] and settings["user"]: settings["password"] = keyring.get_password(self.NAME, str(settings["subdomain"])+"_"+str(settings["user"])) self._settings[group] = settings settings = self._settings[group] if asString: for setting in settings: if not isinstance(settings[setting], bool): settings[setting] = str(settings[setting]) if settings[setting] else "" return settings def setSettings(self, group, settings): self._settings[group] = settings; self._qsettings.beginGroup(group); for setting in self._settings[group]: if group != "connection" or setting != "password": self._qsettings.setValue(setting, settings[setting]) elif settings["subdomain"] and settings["user"]: keyring.set_password(self.NAME, settings["subdomain"]+"_"+settings["user"], settings[setting]) self._qsettings.endGroup(); if group == "connection": self._canConnect = False if settings["subdomain"] and settings["user"] and settings["password"]: self._canConnect = True self._updateLayout() def exit(self): self._forceClose = True self.close() def changeEvent(self, event): if self.getSetting("program", "minimize") and event.type() == QtCore.QEvent.WindowStateChange and self.isMinimized(): self.hide() event.ignore() else: event.accept() def closeEvent(self, event): if (not hasattr(self, "_forceClose") or not self._forceClose) and self.getSetting("program", "minimize"): self.hide() event.ignore() else: if self.getSetting("connection", "join"): self.setSetting("connection", "rooms", ",".join([str(roomId) for roomId in self._rooms.keys()])) self.disconnectNow() if hasattr(self, "_workers") and self._workers: for worker in self._workers: worker.terminate() worker.wait() if hasattr(self, "_worker") and self._worker: self._worker.terminate() self._worker.wait() self.setSetting("window", "size", self.size()) self.setSetting("window", "position", self.pos()) event.accept() def alerts(self): dialog = AlertsDialog(self) dialog.open() def options(self): dialog = OptionsDialog(self) dialog.open() def connectNow(self): if not self._canConnect: return self._connecting = True self.statusBar().showMessage(self._("Connecting with Campfire...")) self._updateLayout() settings = self.getSettings("connection") self._worker = CampfireWorker(settings["subdomain"], settings["user"], settings["password"], settings["ssl"], self) self._connectWorkerSignals(self._worker) self._worker.connect() def disconnectNow(self): self.statusBar().showMessage(self._("Disconnecting from Campfire...")) if self._worker and hasattr(self, "_rooms"): # Using keys() since the dict could be changed (by _cfRoomLeft()) # while iterating on it for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["room"]: self._worker.leave(self._rooms[roomId]["room"], False) self._cfDisconnected() self._updateLayout() def joinRoom(self, roomIndex=None): room = self._roomInIndex(roomIndex if roomIndex else self._toolBar["rooms"].currentIndex()) if not room: return self._toolBar["join"].setEnabled(False) self.statusBar().showMessage(self._("Joining room %s...") % room["name"]) self._rooms[room["id"]] = { "room": None, "stream": None, "upload": None, "tab": None, "editor": None, "usersList": None, "topicLabel": None, "filesLabel": None, "uploadButton": None, "uploadLabel": None, "uploadWidget": None, "newMessages": 0 } self._getWorker().join(room["id"]) def speak(self): message = self._editor.document().toPlainText() room = self.getCurrentRoom() if not room or message.trimmed().isEmpty(): return self.statusBar().showMessage(self._("Sending message to %s...") % room.name) self._getWorker().speak(room, unicode(message)) self._editor.document().clear() def uploadFile(self): room = self.getCurrentRoom() if not room: return path = QtGui.QFileDialog.getOpenFileName(self, self._("Select file to upload")) if path: self._upload(room, str(path)) def uploadCancel(self): room = self.getCurrentRoom() if not room: return if self._rooms[room.id]["upload"]: self._rooms[room.id]["upload"].stop().join() self._rooms[room.id]["upload"] = None self._rooms[room.id]["uploadWidget"].hide() def leaveRoom(self, roomId): if roomId in self._rooms: self.statusBar().showMessage(self._("Leaving room %s...") % self._rooms[roomId]["room"].name) self._getWorker().leave(self._rooms[roomId]["room"]) def changeTopic(self): room = self.getCurrentRoom() if not room: return topic, ok = QtGui.QInputDialog.getText(self, self._("Change topic"), self._("Enter new topic for room %s") % room.name, QtGui.QLineEdit.Normal, room.topic ) if ok: self.statusBar().showMessage(self._("Changing topic for room %s...") % room.name) self._getWorker().changeTopic(room, topic) def updateRoomUsers(self, roomId = None): if not roomId: room = self.getCurrentRoom() if room: roomId = room.id if roomId in self._rooms: self.statusBar().showMessage(self._("Getting users in %s...") % self._rooms[roomId]["room"].name) self._getWorker().users(self._rooms[roomId]["room"]) def updateRoomUploads(self, roomId = None): if not roomId: room = self.getCurrentRoom() if room: roomId = room.id if roomId in self._rooms: self.statusBar().showMessage(self._("Getting uploads in %s...") % self._rooms[roomId]["room"].name) self._getWorker().uploads(self._rooms[roomId]["room"]) def getCurrentRoom(self): index = self._tabs.currentIndex() for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["tab"] == index: return self._rooms[roomId]["room"] def _cfStreamMessage(self, room, message, live=True, updateRoom=True): if ( not message.user or (live and message.is_text() and message.is_by_current_user()) or not room.id in self._rooms ): return user = message.user.name notify = True alert = False if message.is_text() and not message.is_by_current_user(): alert = self._matchesAlert(message.body) html = None if message.is_joining(): html = "<font color=\"#%s\">" % self.COLORS["join"] html += "--> %s joined %s" % (user, room.name) html += "</font>" elif message.is_leaving(): html = "<font color=\"#%s\">" % self.COLORS["leave"] html += "<-- %s has left %s" % (user, room.name) html += "</font>" elif message.is_text(): body = self._plainTextToHTML(message.tweet["tweet"] if message.is_tweet() else message.body) if message.is_tweet(): body = "<a href=\"%s\">%s</a> <a href=\"%s\">tweeted</a>: %s" % ( "http://twitter.com/%s" % message.tweet["user"], message.tweet["user"], message.tweet["url"], body ) elif message.is_paste(): body = "<br /><hr /><code>%s</code><hr />" % body else: body = self._autoLink(body) created = QtCore.QDateTime( message.created_at.year, message.created_at.month, message.created_at.day, message.created_at.hour, message.created_at.minute, message.created_at.second ) created.setTimeSpec(QtCore.Qt.UTC) createdFormat = "h:mm ap" if created.daysTo(QtCore.QDateTime.currentDateTime()): createdFormat = "MMM d, %s" % createdFormat html = "<font color=\"#%s\">[%s]</font> " % (self.COLORS["time"], created.toLocalTime().toString(createdFormat)) if alert: html += "<font color=\"#%s\">" % self.COLORS["alert"] else: html += "<font color=\"#%s\">" % self.COLORS["message"] if message.is_by_current_user(): html += "<font color=\"#%s\">" % self.COLORS["nickSelf"] elif alert: html += "<font color=\"#%s\">" % self.COLORS["nickAlert"] else: html += "<font color=\"#%s\">" % self.COLORS["nick"] html += "%s" % ("<strong>%s</strong>" % user if alert else user) html += "</font>: " html += body html += "</font>" elif message.is_upload(): html = "<font color=\"#%s\">" % self.COLORS["upload"] html += "<strong>%s</strong> uploaded <a href=\"%s\">%s</a>" % ( user, message.upload["url"], message.upload["name"] ) html += "</font>" elif message.is_topic_change(): html = "<font color=\"#%s\">" % self.COLORS["leave"] html += "%s changed topic to <strong>%s</strong>" % (user, message.body) html += "</font>" if html: html = "%s<br />" % html editor = self._rooms[room.id]["editor"] if not editor: return scrollbar = editor.verticalScrollBar() currentScrollbarValue = scrollbar.value() autoScroll = (currentScrollbarValue == scrollbar.maximum()) editor.moveCursor(QtGui.QTextCursor.End) editor.textCursor().insertHtml(html) if autoScroll: scrollbar.setValue(scrollbar.maximum()) else: scrollbar.setValue(currentScrollbarValue) tabIndex = self._rooms[room.id]["tab"] tabBar = self._tabs.tabBar() isActiveTab = (self.isActiveWindow() and tabIndex == self._tabs.currentIndex()) if message.is_text() and not isActiveTab: self._rooms[room.id]["newMessages"] += 1 if self._rooms[room.id]["newMessages"] > 0: tabBar.setTabText(tabIndex, "%s (%s)" % (room.name, self._rooms[room.id]["newMessages"])) if not isActiveTab and (alert or self._rooms[room.id]["newMessages"] > 0) and tabBar.tabTextColor(tabIndex) == self.COLORS["tabs"]["normal"]: tabBar.setTabTextColor(tabIndex, self.COLORS["tabs"]["alert" if alert else "new"]) if alert: if not isActiveTab: self._trayIcon.alert() if notify: self._notify(room, message.body) if updateRoom: if (message.is_joining() or message.is_leaving()): self.updateRoomUsers(room.id) elif message.is_upload(): self.updateRoomUploads(room.id) elif message.is_topic_change() and not message.is_by_current_user(): self._cfTopicChanged(room, message.body) def _matchesAlert(self, message): matches = False regexes = [] words = [ "Mariano Iglesias", "Mariano" ] for word in words: regexes.append("\\b%s\\b" % word) for regex in regexes: if QtCore.QString(message).contains(QtCore.QRegExp(regex, QtCore.Qt.CaseInsensitive)): matches = True break return matches def _cfConnected(self, user, rooms): self._connecting = False self._connected = True self._rooms = {} self._toolBar["rooms"].clear() for room in rooms: self._toolBar["rooms"].addItem(room["name"], room) self.statusBar().showMessage(self._("%s connected to Campfire") % user.name, 5000) self._updateLayout() if self.getSetting("connection", "join"): rooms = self.getSetting("connection", "rooms") if rooms: for roomId in rooms.split(","): count = self._toolBar["rooms"].count() if count: roomIndex = None for i in range(count): data = self._toolBar["rooms"].itemData(i) if not data.isNull(): data = data.toMap() for key in data: if str(key) == "id" and str(data[key].toString()) == roomId: roomIndex = i break; if roomIndex is not None: break if roomIndex is not None: self.joinRoom(roomIndex) def _cfDisconnected(self): self._connecting = False self._connected = False self._rooms = {} self._worker = None self.statusBar().clearMessage() def _cfRoomJoined(self, room, messages=[]): if room.id not in self._rooms: return self._rooms[room.id].update(self._setupRoomUI(room)) self._rooms[room.id]["room"] = room self._rooms[room.id]["stream"] = self._worker.getStream(room) self.updateRoomUsers(room.id) self.updateRoomUploads(room.id) self.statusBar().showMessage(self._("Joined room %s") % room.name, 5000) self._updatedRoomsList() if messages: for message in messages: self._cfStreamMessage(room, message, live=False, updateRoom=False) def _cfSpoke(self, room, message): self._cfStreamMessage(room, message, live=False) self.statusBar().clearMessage() def _cfRoomLeft(self, room): if self._rooms[room.id]["stream"]: self._rooms[room.id]["stream"].stop().join() if self._rooms[room.id]["upload"]: self._rooms[room.id]["upload"].stop().join() self._tabs.removeTab(self._rooms[room.id]["tab"]) del self._rooms[room.id] self.statusBar().showMessage(self._("Left room %s") % room.name, 5000) self._updatedRoomsList() def _cfRoomUsers(self, room, users): # We may be disconnecting while still processing the list if not room.id in self._rooms: return self.statusBar().clearMessage() self._rooms[room.id]["usersList"].clear() for user in users: item = QtGui.QListWidgetItem(user["name"]) item.setData(QtCore.Qt.UserRole, user) self._rooms[room.id]["usersList"].addItem(item) def _cfRoomUploads(self, room, uploads): # We may be disconnecting while still processing the list if not room.id in self._rooms: return self.statusBar().clearMessage() label = self._rooms[room.id]["filesLabel"] if uploads: html = "" for upload in uploads: html += "%s• <a href=\"%s\">%s</a>" % ( "<br />" if html else "", upload["full_url"], upload["name"] ) html = "%s<br />%s" % ( self._("Latest uploads:"), html ) label.setText(html) if not label.isVisible(): label.show() elif label.isVisible(): label.setText("") label.hide() def _cfUploadProgress(self, room, current, total): if not room.id in self._rooms: return progressBar = self._rooms[room.id]["uploadProgressBar"] if not self._rooms[room.id]["uploadWidget"].isVisible(): self._rooms[room.id]["uploadWidget"].show() progressBar.setMaximum(total) progressBar.setValue(current) def _cfUploadFinished(self, room): if not room.id in self._rooms: return self._rooms[room.id]["upload"].join() self._rooms[room.id]["upload"] = None self._rooms[room.id]["uploadWidget"].hide() def _cfTopicChanged(self, room, topic): if not room.id in self._rooms: return self._rooms[room.id]["topicLabel"].setText(topic) self.statusBar().clearMessage() def _cfConnectError(self, error): self._cfDisconnected() self._updateLayout() self._cfError(error) def _cfError(self, error): self.statusBar().clearMessage() QtGui.QMessageBox.critical(self, "Error", str(error)) def _roomSelected(self, index): self._updatedRoomsList(index) def _upload(self, room, path): self._rooms[room.id]["upload"] = self._worker.upload(room, path) self._updateRoomLayout() def _roomTabClose(self, tabIndex): for roomId in self._rooms: if self._rooms[roomId]["tab"] == tabIndex: self.leaveRoom(roomId) break def _roomTabFocused(self): tabIndex = self._tabs.currentIndex() if tabIndex < 0 or not self.isActiveWindow(): return room = self._roomInTabIndex(tabIndex) if not room: return tabBar = self._tabs.tabBar() if self._rooms[room.id]["newMessages"] > 0: self._rooms[room.id]["newMessages"] = 0 tabBar.setTabText(tabIndex, room.name) if tabBar.tabTextColor(tabIndex) != self.COLORS["tabs"]["normal"]: tabBar.setTabTextColor(tabIndex, self.COLORS["tabs"]["normal"]) self._updateRoomLayout() def _roomInTabIndex(self, index): room = None for key in self._rooms: if self._rooms[key]["tab"] == index: room = self._rooms[key]["room"] break return room def _roomInIndex(self, index): room = {} data = self._toolBar["rooms"].itemData(index) if not data.isNull(): data = data.toMap() for key in data: room[str(key)] = u"%s" % data[key].toString() return room def _connectWorkerSignals(self, worker): self.connect(worker, QtCore.SIGNAL("error(PyQt_PyObject)"), self._cfError) self.connect(worker, QtCore.SIGNAL("connected(PyQt_PyObject, PyQt_PyObject)"), self._cfConnected) self.connect(worker, QtCore.SIGNAL("connectError(PyQt_PyObject)"), self._cfConnectError) self.connect(worker, QtCore.SIGNAL("joined(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomJoined) self.connect(worker, QtCore.SIGNAL("spoke(PyQt_PyObject, PyQt_PyObject)"), self._cfSpoke) self.connect(worker, QtCore.SIGNAL("streamMessage(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfStreamMessage) self.connect(worker, QtCore.SIGNAL("left(PyQt_PyObject)"), self._cfRoomLeft) self.connect(worker, QtCore.SIGNAL("users(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUsers) self.connect(worker, QtCore.SIGNAL("uploads(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUploads) self.connect(worker, QtCore.SIGNAL("uploadProgress(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfUploadProgress) self.connect(worker, QtCore.SIGNAL("uploadFinished(PyQt_PyObject)"), self._cfUploadFinished) self.connect(worker, QtCore.SIGNAL("topicChanged(PyQt_PyObject, PyQt_PyObject)"), self._cfTopicChanged) def _getWorker(self): if not hasattr(self, "_workers"): self._workers = [] if self._workers: for worker in self._workers: if worker.isFinished(): return worker worker = copy.copy(self._worker) self._connectWorkerSignals(worker) self._workers.append(worker) return worker def _updatedRoomsList(self, index=None): if not index: index = self._toolBar["rooms"].currentIndex() room = self._roomInIndex(index) self._toolBar["join"].setEnabled(False) if not room or room["id"] not in self._rooms: self._toolBar["join"].setEnabled(True) centralWidget = self.centralWidget() if not self._tabs.count(): centralWidget.hide() else: centralWidget.show() def _notify(self, room, message): raise NotImplementedError("_notify() must be implemented") def _updateRoomLayout(self): room = self.getCurrentRoom() if room: canUpload = not self._rooms[room.id]["upload"] uploadButton = self._rooms[room.id]["uploadButton"] if ( (canUpload and not uploadButton.isEnabled()) or (not canUpload and uploadButton.isEnabled()) ): uploadButton.setEnabled(canUpload) def _updateLayout(self): self._menus["file"]["connect"].setEnabled(not self._connected and self._canConnect and not self._connecting) self._menus["file"]["disconnect"].setEnabled(self._connected) roomsEmpty = self._toolBar["rooms"].count() == 1 and self._toolBar["rooms"].itemData(0).isNull() if not roomsEmpty and (not self._connected or not self._toolBar["rooms"].count()): self._toolBar["rooms"].clear() self._toolBar["rooms"].addItem(self._("No rooms available")) self._toolBar["rooms"].setEnabled(False) elif not roomsEmpty: self._toolBar["rooms"].setEnabled(True) self._toolBar["roomsLabel"].setEnabled(self._toolBar["rooms"].isEnabled()) self._toolBar["join"].setEnabled(self._toolBar["rooms"].isEnabled()) def _setupRoomUI(self, room): topicLabel = ClickableQLabel(room.topic) topicLabel.setToolTip(self._("Click here to change room's topic")) topicLabel.setWordWrap(True) self.connect(topicLabel, QtCore.SIGNAL("clicked()"), self.changeTopic) editor = QtGui.QTextBrowser() editor.setOpenExternalLinks(True) usersList = QtGui.QListWidget() filesLabel = QtGui.QLabel("") filesLabel.setOpenExternalLinks(True) filesLabel.setWordWrap(True) filesLabel.hide() uploadButton = QtGui.QPushButton(self._("&Upload new file")) self.connect(uploadButton, QtCore.SIGNAL("clicked()"), self.uploadFile) uploadProgressBar = QtGui.QProgressBar() uploadProgressLabel = QtGui.QLabel(self._("Uploading:")) uploadCancelButton = QtGui.QPushButton(self._("Cancel")) self.connect(uploadCancelButton, QtCore.SIGNAL("clicked()"), self.uploadCancel) uploadLayout = QtGui.QHBoxLayout() uploadLayout.addWidget(uploadProgressLabel) uploadLayout.addWidget(uploadProgressBar) uploadLayout.addWidget(uploadCancelButton) uploadWidget = QtGui.QWidget() uploadWidget.setLayout(uploadLayout) uploadWidget.hide() leftFrameLayout = QtGui.QVBoxLayout() leftFrameLayout.addWidget(topicLabel) leftFrameLayout.addWidget(editor) leftFrameLayout.addWidget(uploadWidget) rightFrameLayout = QtGui.QVBoxLayout() rightFrameLayout.addWidget(QtGui.QLabel(self._("Users in room:"))) rightFrameLayout.addWidget(usersList) rightFrameLayout.addWidget(filesLabel) rightFrameLayout.addWidget(uploadButton) rightFrameLayout.addStretch(1) leftFrame = QtGui.QWidget() leftFrame.setLayout(leftFrameLayout) rightFrame = QtGui.QWidget() rightFrame.setLayout(rightFrameLayout) splitter = QtGui.QSplitter() splitter.addWidget(leftFrame) splitter.addWidget(rightFrame) splitter.setSizes([splitter.size().width() * 0.75, splitter.size().width() * 0.25]) index = self._tabs.addTab(splitter, room.name) self._tabs.setCurrentIndex(index) if not self.COLORS["tabs"]["normal"]: self.COLORS["tabs"]["normal"] = self._tabs.tabBar().tabTextColor(index) else: self._tabs.tabBar().setTabTextColor(index, self.COLORS["tabs"]["normal"]) return { "tab": index, "editor": editor, "usersList": usersList, "topicLabel": topicLabel, "filesLabel": filesLabel, "uploadButton": uploadButton, "uploadWidget": uploadWidget, "uploadProgressBar": uploadProgressBar, "uploadProgressLabel": uploadProgressLabel } def _setupUI(self): self.setWindowTitle(self.NAME) self._addMenu() self._addToolbar() self._tabs = QtGui.QTabWidget() self._tabs.setTabsClosable(True) self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused) self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose) self._editor = QtGui.QPlainTextEdit() self._editor.setFixedHeight(self._editor.fontMetrics().height() * 2) self._editor.installEventFilter(SuggesterKeyPressEventFilter(self, Suggester(self._editor))) speakButton = QtGui.QPushButton(self._("&Send")) self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak) grid = QtGui.QGridLayout() grid.setRowStretch(0, 1) grid.addWidget(self._tabs, 0, 0, 1, -1) grid.addWidget(self._editor, 1, 0) grid.addWidget(speakButton, 1, 1) widget = QtGui.QWidget() widget.setLayout(grid) self.setCentralWidget(widget) tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self) self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused) widget.installEventFilter(tabWidgetFocusEventFilter) self.centralWidget().hide() size = self.getSetting("window", "size") if not size: size = QtCore.QSize(640, 480) self.resize(size) position = self.getSetting("window", "position") if not position: screen = QtGui.QDesktopWidget().screenGeometry() position = QtCore.QPoint((screen.width()-size.width())/2, (screen.height()-size.height())/2) self.move(position) self._updateLayout() menu = QtGui.QMenu(self) menu.addAction(self._menus["file"]["connect"]) menu.addAction(self._menus["file"]["disconnect"]) menu.addSeparator() menu.addAction(self._menus["file"]["exit"]) self._trayIcon = Systray(self._icon, self) self._trayIcon.setContextMenu(menu) self._trayIcon.setToolTip(self.DESCRIPTION) def _addMenu(self): self._menus = { "file": { "connect": self._createAction(self._("&Connect"), self.connectNow, icon="connect.png"), "disconnect": self._createAction(self._("&Disconnect"), self.disconnectNow, icon="disconnect.png"), "exit": self._createAction(self._("E&xit"), self.exit) }, "settings": { "alerts": self._createAction(self._("&Alerts..."), self.alerts, icon="alerts.png"), "options": self._createAction(self._("&Options..."), self.options) }, "help": { "about": self._createAction(self._("A&bout")) } } menu = self.menuBar() file_menu = menu.addMenu(self._("&File")) file_menu.addAction(self._menus["file"]["connect"]) file_menu.addAction(self._menus["file"]["disconnect"]) file_menu.addSeparator() file_menu.addAction(self._menus["file"]["exit"]) settings_menu = menu.addMenu(self._("S&ettings")) settings_menu.addAction(self._menus["settings"]["alerts"]) settings_menu.addSeparator() settings_menu.addAction(self._menus["settings"]["options"]) help_menu = menu.addMenu(self._("&Help")) help_menu.addAction(self._menus["help"]["about"]) def _addToolbar(self): self._toolBar = { "connect": self._menus["file"]["connect"], "disconnect": self._menus["file"]["disconnect"], "roomsLabel": QtGui.QLabel(self._("Rooms:")), "rooms": QtGui.QComboBox(), "join": self._createAction(self._("Join room"), self.joinRoom, icon="join.png"), "alerts": self._menus["settings"]["alerts"] } self.connect(self._toolBar["rooms"], QtCore.SIGNAL("currentIndexChanged(int)"), self._roomSelected) toolBar = self.toolBar() toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) toolBar.addAction(self._toolBar["connect"]) toolBar.addAction(self._toolBar["disconnect"]) toolBar.addSeparator(); toolBar.addWidget(self._toolBar["roomsLabel"]) toolBar.addWidget(self._toolBar["rooms"]) toolBar.addAction(self._toolBar["join"]) toolBar.addSeparator(); toolBar.addAction(self._toolBar["alerts"]) def _createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): """ Create an action """ action = QtGui.QAction(text, self) if icon is not None: if not isinstance(icon, QtGui.QIcon): action.setIcon(QtGui.QIcon(":/icons/%s" % (icon))) else: action.setIcon(icon) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, QtCore.SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def _plainTextToHTML(self, string): return string.replace("<", "<").replace(">", ">").replace("\n", "<br />") def _autoLink(self, string): urlre = re.compile("(\(?https?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|])(\">|</a>)?") urls = urlre.findall(string) cleanUrls = [] for url in urls: if url[1]: continue currentUrl = url[0] if currentUrl[0] == '(' and currentUrl[-1] == ')': currentUrl = currentUrl[1:-1] if currentUrl in cleanUrls: continue cleanUrls.append(currentUrl) string = re.sub("(?<!(=\"|\">))" + re.escape(currentUrl), "<a href=\"" + currentUrl + "\">" + currentUrl + "</a>", string) return string
class ScudCloud(QtGui.QMainWindow): APP_NAME = "ScudCloud Slack_SSB" SIGNIN_URL = "https://slack.com/signin" debug = False forceClose = False messages = 0 def __init__(self, parent = None, settings_path = "~/.config/scudcloud"): super(ScudCloud, self).__init__(parent) self.setWindowTitle('ScudCloud') self.settings_path = settings_path self.notifier = Notifier(self.APP_NAME, get_resource_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.leftPane = LeftPane(self) self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) self.stackedWidget = QtGui.QStackedWidget() self.stackedWidget.addWidget(webView) centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.addMenu() self.tray = Systray(self) self.systray(ScudCloud.minimized) self.installEventFilter(self) if self.identifier is None: webView.load(QtCore.QUrl(self.SIGNIN_URL)) else: webView.load(QtCore.QUrl(self.domain())) webView.show() def systray(self, show=None): if show is None: show = self.settings.value("Systray") == "True" if show: self.tray.show() self.menus["file"]["close"].setEnabled(True) self.settings.setValue("Systray", "True") else: self.tray.setVisible(False) self.menus["file"]["close"].setEnabled(False) self.settings.setValue("Systray", "False") def readZoom(self): default = 1 if self.settings.value("Zoom") is not None: default = float(self.settings.value("Zoom")) return default def setZoom(self, factor=1): if factor > 0: for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) widget.setZoomFactor(factor) self.settings.setValue("Zoom", factor) def zoomIn(self): self.setZoom(self.current().zoomFactor() + 0.1) def zoomOut(self): self.setZoom(self.current().zoomFactor() - 0.1) def zoomReset(self): self.setZoom() def addMenu(self): self.menus = { "file": { "preferences": self.createAction("Preferences", self.current().preferences), "systray": self.createAction("Close to Tray", self.systray, None, True), "addTeam": self.createAction("Sign in to Another Team", self.current().addTeam), "signout": self.createAction("Signout", self.current().logout), "close": self.createAction("Close", self.close, QKeySequence.Close), "exit": self.createAction("Quit", self.exit, QKeySequence.Quit) }, "edit": { "undo": self.current().pageAction(QtWebKit.QWebPage.Undo), "redo": self.current().pageAction(QtWebKit.QWebPage.Redo), "cut": self.current().pageAction(QtWebKit.QWebPage.Cut), "copy": self.current().pageAction(QtWebKit.QWebPage.Copy), "paste": self.current().pageAction(QtWebKit.QWebPage.Paste), "reload": self.current().pageAction(QtWebKit.QWebPage.Reload) }, "view": { "zoomin": self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn), "zoomout": self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut), "reset": self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0) }, "help": { "help": self.createAction("Help and Feedback", self.current().help, QKeySequence.HelpContents), "center": self.createAction("Slack Help Center", self.current().helpCenter), "about": self.createAction("About", self.current().about) } } menu = self.menuBar() fileMenu = menu.addMenu("&File") fileMenu.addAction(self.menus["file"]["preferences"]) fileMenu.addAction(self.menus["file"]["systray"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["addTeam"]) fileMenu.addAction(self.menus["file"]["signout"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["close"]) fileMenu.addAction(self.menus["file"]["exit"]) editMenu = menu.addMenu("&Edit") editMenu.addAction(self.menus["edit"]["undo"]) editMenu.addAction(self.menus["edit"]["redo"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["cut"]) editMenu.addAction(self.menus["edit"]["copy"]) editMenu.addAction(self.menus["edit"]["paste"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["reload"]) viewMenu = menu.addMenu("&View") viewMenu.addAction(self.menus["view"]["zoomin"]) viewMenu.addAction(self.menus["view"]["zoomout"]) viewMenu.addAction(self.menus["view"]["reset"]) helpMenu = menu.addMenu("&Help") helpMenu.addAction(self.menus["help"]["help"]) helpMenu.addAction(self.menus["help"]["center"]) helpMenu.addSeparator() helpMenu.addAction(self.menus["help"]["about"]) self.enableMenus(False) showSystray = self.settings.value("Systray") == "True" self.menus["file"]["systray"].setChecked(showSystray) self.menus["file"]["close"].setEnabled(showSystray) def enableMenus(self, enabled): self.menus["file"]["preferences"].setEnabled(enabled == True) self.menus["file"]["addTeam"].setEnabled(enabled == True) self.menus["file"]["signout"].setEnabled(enabled == True) self.menus["help"]["help"].setEnabled(enabled == True) def createAction(self, text, slot, shortcut=None, checkable=False): action = QtGui.QAction(text, self) if shortcut is not None: action.setShortcut(shortcut) action.triggered.connect(slot) if checkable: action.setCheckable(True) return action def domain(self): if self.identifier.endswith(".slack.com"): return self.identifier else: return "https://"+self.identifier+".slack.com" def current(self): return self.stackedWidget.currentWidget() def teams(self, teams): if teams is not None and len(teams) > 1: self.leftPane.show() for t in teams: try: self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], t['team_icon']['image_88'], t == teams[0]) except: self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], '', t == teams[0]) def switchTo(self, url): index = -1 for i in range(0, self.stackedWidget.count()): if self.stackedWidget.widget(i).url().toString().startswith(url): index = i break if index != -1: self.stackedWidget.setCurrentIndex(index) else: webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) webView.load(QtCore.QUrl(url)) webView.show() self.stackedWidget.addWidget(webView) self.stackedWidget.setCurrentWidget(webView) self.quicklist(self.current().listChannels()) self.enableMenus(self.current().isConnected()) def eventFilter(self, obj, event): if event.type() == QtCore.QEvent.ActivationChange and self.isActiveWindow(): self.focusInEvent(event) if event.type() == QtCore.QEvent.KeyPress and QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier: if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0) elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1) elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2) elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3) elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4) elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5) elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6) elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7) elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8) return QtGui.QMainWindow.eventFilter(self, obj, event); def focusInEvent(self, event): self.launcher.set_property("urgent", False) self.tray.stopAlert() def titleChanged(self): self.setWindowTitle(self.current().title()) def closeEvent(self, event): if not self.forceClose and self.settings.value("Systray") == "True": self.hide() event.ignore() else: self.cookiesjar.save() self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) def show(self): self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.activateWindow() self.setVisible(True) def exit(self): self.forceClose = True self.close() def quicklist(self, channels): if Dbusmenu is not None: ql = Dbusmenu.Menuitem.new() self.launcher.set_property("quicklist", ql) if channels is not None: for c in channels: if c['is_member']: item = Dbusmenu.Menuitem.new () item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, "#"+c['name']) item.property_set ("id", c['name']) item.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True) item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel) ql.child_append(item) self.launcher.set_property("quicklist", ql) def notify(self, title, message): self.notifier.notify(title, message) self.alert() def alert(self): if not self.isActiveWindow(): self.launcher.set_property("urgent", True) self.tray.alert() def count(self): total = 0 for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) if widget.messages == 0: self.leftPane.stopAlert(widget.team()) else: self.leftPane.alert(widget.team()) total+=widget.messages if total > self.messages: self.alert() if 0 == total: self.launcher.set_property("count_visible", False) self.tray.setCounter(0) else: self.tray.setCounter(total) self.launcher.set_property("count", total) self.launcher.set_property("count_visible", True) self.messages = total
class Snakefire(object): DOMAIN = "www.snakefire.org" NAME = "Snakefire" DESCRIPTION = "Snakefire: Campfire Linux Client" VERSION = "1.0.3" ICON = "snakefire.png" COLORS = { "normal": None, "new": QtGui.QColor(0, 0, 255), "alert": QtGui.QColor(255, 0, 0) } def __init__(self): self.DESCRIPTION = self._(self.DESCRIPTION) self._pingTimer = None self._idleTimer = None self._idle = False self._lastIdleAnswer = None self._worker = None self._settings = {} self._canConnect = False self._cfDisconnected() if len(sys.argv) > 1: self._qsettings = QtCore.QSettings(sys.argv[1], QtCore.QSettings.IniFormat if sys.platform.find("win") == 0 else QtCore.QSettings.NativeFormat) else: self._qsettings = QtCore.QSettings(self.NAME, self.NAME) self._icon = QtGui.QIcon(":/icons/{icon}".format(icon=self.ICON)) self.setWindowIcon(self._icon) self.setAcceptDrops(True) self._setupUI() settings = self.getSettings("connection") self._canConnect = False if settings["subdomain"] and settings["user"] and settings["password"]: self._canConnect = True self._updateLayout() if not self._canConnect: self.options() elif settings["connect"]: self.connectNow() def showEvent(self, event): if self._trayIcon.isVisible(): if self._trayIcon.isAlerting(): self._trayIcon.stopAlert() return self._trayIcon.show() def dragEnterEvent(self, event): room = self.getCurrentRoom() canUpload = not self._rooms[room.id]["upload"] if room else False if canUpload and self._getDropFile(event): event.acceptProposedAction() def dropEvent(self, event): room = self.getCurrentRoom() path = self._getDropFile(event) if room and path: self._upload(room, path) def _getDropFile(self, event): files = [] urls = event.mimeData().urls() if urls: for url in urls: path = url.path() if path and os.path.exists(path) and os.path.isfile(path): try: handle = open(str(path)) handle.close() files.append(str(path)) except: pass if len(files) > 1: files = [] return files[0] if files else None def getSetting(self, group, setting): settings = self.getSettings(group, asString=False); return settings[setting] if setting in settings else None def setSetting(self, group, setting, value): self._qsettings.beginGroup(group); self._qsettings.setValue(setting, value) self._qsettings.endGroup(); def getSettings(self, group, asString=True, reload=False): defaults = { "connection": { "subdomain": None, "user": None, "password": None, "ssl": False, "connect": False, "join": False, "rooms": [] }, "program": { "minimize": False, "spell_language": SpellTextEditor.defaultLanguage(), "away": True, "away_time": 10, "away_time_between_messages": 5, "away_message": self._("I am currently away from {name}").format(name=self.NAME) }, "display": { "theme": "default", "size": 100, "show_join_message": True, "show_part_message": True, "show_message_timestamps": True }, "alerts": { "notify_ping": True, "notify_inactive_tab": False, "notify_blink": True, "notify_notify": True }, "matches": [] } if reload or not group in self._settings: settings = defaults[group] if group in defaults else {} if group == "matches": settings = [] size = self._qsettings.beginReadArray("matches") for i in range(size): self._qsettings.setArrayIndex(i) isRegex = False try: isRegex = True if ["true", "1"].index(str(self._qsettings.value("regex").toPyObject()).lower()) >= 0 else False except: pass settings.append({ 'regex': isRegex, 'match': self._qsettings.value("match").toPyObject() }) self._qsettings.endArray() else: self._qsettings.beginGroup(group); for setting in self._qsettings.childKeys(): settings[str(setting)] = self._qsettings.value(setting).toPyObject() self._qsettings.endGroup(); boolSettings = [] if group == "connection": boolSettings += ["ssl", "connect", "join"] elif group == "program": boolSettings += ["away", "minimize"] elif group == "display": boolSettings += ["show_join_message", "show_part_message", "show_message_timestamps"] elif group == "alerts": boolSettings += ["notify_ping", "notify_inactive_tab", "notify_blink", "notify_notify"] for boolSetting in boolSettings: try: settings[boolSetting] = True if ["true", "1"].index(str(settings[boolSetting]).lower()) >= 0 else False except: settings[boolSetting] = False if group == "connection" and settings["subdomain"] and settings["user"]: settings["password"] = keyring.get_password(self.NAME, str(settings["subdomain"])+"_"+str(settings["user"])) self._settings[group] = settings settings = self._settings[group] if asString: if isinstance(settings, list): for i, row in enumerate(settings): for setting in row: if not isinstance(row[setting], bool): settings[i][setting] = str(row[setting]) if row[setting] else "" else: for setting in settings: if not isinstance(settings[setting], bool): settings[setting] = str(settings[setting]) if settings[setting] else "" return settings def setSettings(self, group, settings): self._settings[group] = settings; if group == "matches": self._qsettings.beginWriteArray("matches") for i, setting in enumerate(settings): self._qsettings.setArrayIndex(i) self._qsettings.setValue("regex", setting["regex"]) self._qsettings.setValue("match", setting["match"]) self._qsettings.endArray() else: self._qsettings.beginGroup(group); for setting in self._settings[group]: if group != "connection" or setting != "password": self._qsettings.setValue(setting, settings[setting]) elif settings["subdomain"] and settings["user"]: keyring.set_password(self.NAME, settings["subdomain"]+"_"+settings["user"], settings[setting]) self._qsettings.endGroup(); if group == "connection": self._canConnect = False if settings["subdomain"] and settings["user"] and settings["password"]: self._canConnect = True self._updateLayout() elif group == "program": if settings["away"] and self._connected: self._setUpIdleTracker() else: self._setUpIdleTracker(False) if self._editor: if settings["spell_language"]: self._editor.enableSpell(settings["spell_language"]) else: self._editor.disableSpell() elif group == "display": for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["view"]: self._rooms[roomId]["view"].updateTheme(settings["theme"], settings["size"]) def exit(self): self._forceClose = True self.close() def changeEvent(self, event): if self.getSetting("program", "minimize") and event.type() == QtCore.QEvent.WindowStateChange and self.isMinimized(): self.hide() event.ignore() else: event.accept() def closeEvent(self, event): if (not hasattr(self, "_forceClose") or not self._forceClose) and self.getSetting("program", "minimize"): self.hide() event.ignore() else: if self.getSetting("connection", "join"): self.setSetting("connection", "rooms", ",".join([str(roomId) for roomId in self._rooms.keys()])) self.disconnectNow() if hasattr(self, "_workers") and self._workers: for worker in self._workers: worker.terminate() worker.wait() if hasattr(self, "_worker") and self._worker: self._worker.terminate() self._worker.wait() self.setSetting("window", "size", self.size()) self.setSetting("window", "position", self.pos()) event.accept() def alerts(self): dialog = AlertsDialog(self) dialog.open() def options(self): dialog = OptionsDialog(self) dialog.open() def about(self): dialog = AboutDialog(self) dialog.open() def connectNow(self): if not self._canConnect: return self._connecting = True self.statusBar().showMessage(self._("Connecting with Campfire...")) self._updateLayout() settings = self.getSettings("connection") self._worker = CampfireWorker(settings["subdomain"], settings["user"], settings["password"], settings["ssl"], self) self._connectWorkerSignals(self._worker) self._worker.connect() def disconnectNow(self): self.statusBar().showMessage(self._("Disconnecting from Campfire...")) if self._worker and hasattr(self, "_rooms"): # Using keys() since the dict could be changed (by _cfRoomLeft()) # while iterating on it for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["room"]: self._worker.leave(self._rooms[roomId]["room"], False) self._cfDisconnected() self._updateLayout() def joinRoom(self, roomIndex=None): room = self._roomInIndex(roomIndex if roomIndex else self._toolBar["rooms"].currentIndex()) if not room: return self._toolBar["join"].setEnabled(False) self.statusBar().showMessage(unicode(self._("Joining room {room}...").format(room=room["name"]))) self._rooms[room["id"]] = { "room": None, "stream": None, "upload": None, "tab": None, "view": None, "frame": None, "usersList": None, "topicLabel": None, "filesLabel": None, "uploadButton": None, "uploadLabel": None, "uploadWidget": None, "newMessages": 0 } self._getWorker().join(room["id"]) def ping(self): if not self._connected: return for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["room"]: self.updateRoomUsers(roomId, pinging=True) def speak(self): message = self._editor.document().toPlainText() room = self.getCurrentRoom() if not room or message.trimmed().isEmpty(): return self._editor.document().clear() if message[0] == '/': command = QtCore.QString(message) separatorIndex = command.indexOf(QtCore.QRegExp('\\s')); handled = self.command(command.mid(1, separatorIndex-1), command.mid(separatorIndex + 1 if separatorIndex >= 0 else command.length())) if handled: return self.statusBar().showMessage(unicode(self._("Sending message to {room}...").format(room=room.name))) self._getWorker().speak(room, unicode(message)) def command(self, command, args): if command.compare(QtCore.QString("away"), QtCore.Qt.CaseInsensitive) == 0: self.toggleAway() return True def uploadFile(self): room = self.getCurrentRoom() if not room: return path = QtGui.QFileDialog.getOpenFileName(self, self._("Select file to upload")) if path: self._upload(room, str(path)) def uploadCancel(self): room = self.getCurrentRoom() if not room: return if self._rooms[room.id]["upload"]: self._rooms[room.id]["upload"].stop().join() self._rooms[room.id]["upload"] = None self._rooms[room.id]["uploadWidget"].hide() def leaveRoom(self, roomId): if roomId in self._rooms: self.statusBar().showMessage(unicode(self._("Leaving room {room}...").format(room=self._rooms[roomId]["room"].name))) self._getWorker().leave(self._rooms[roomId]["room"]) def changeTopic(self): room = self.getCurrentRoom() if not room: return topic, ok = QtGui.QInputDialog.getText(self, self._("Change topic"), unicode(self._("Enter new topic for room {room}").format(room=room.name)), QtGui.QLineEdit.Normal, room.topic ) if ok: self.statusBar().showMessage(unicode(self._("Changing topic for room {room}...").format(room=room.name))) self._getWorker().changeTopic(room, topic) def updateRoomUsers(self, roomId = None, pinging = False): if not roomId: room = self.getCurrentRoom() if room: roomId = room.id if roomId in self._rooms: if not pinging: self.statusBar().showMessage(unicode(self._("Getting users in {room}...").format(room=self._rooms[roomId]["room"].name))) self._getWorker().users(self._rooms[roomId]["room"], pinging) def updateRoomUploads(self, roomId = None): if not roomId: room = self.getCurrentRoom() if room: roomId = room.id if roomId in self._rooms: self.statusBar().showMessage(unicode(self._("Getting uploads from {room}...").format(room=self._rooms[roomId]["room"].name))) self._getWorker().uploads(self._rooms[roomId]["room"]) def getCurrentRoom(self): index = self._tabs.currentIndex() for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["tab"] == index: return self._rooms[roomId]["room"] def toggleAway(self): self.setAway(False if self._idle else True) def setAway(self, away=True): self._idle = away self.statusBar().showMessage(self._("You are now away") if self._idle else self._('You are now active'), 5000) def onIdle(self): self.setAway(True) def onActive(self): self.setAway(False) def _setUpIdleTracker(self, enable=True): if self._idleTimer: self._idleTimer.stop() self._idleTimer = None if enable and IdleTimer.supported(): self._idleTimer = IdleTimer(self, int(self.getSetting("program", "away_time")) * 60) self.connect(self._idleTimer, QtCore.SIGNAL("idle()"), self.onIdle) self.connect(self._idleTimer, QtCore.SIGNAL("active()"), self.onActive) self._idleTimer.start() def _cfStreamMessage(self, room, message, live=True, updateRoom=True): if ( not message.user or (live and message.is_text() and message.is_by_current_user()) or not room.id in self._rooms ): return view = self._rooms[room.id]["view"] if not view: return alert = False alertIsDirectPing = False if message.is_text() and not message.is_by_current_user(): alertIsDirectPing = (QtCore.QString(message.body).indexOf(QtCore.QRegExp("\\s*\\b{name}\\b".format(name=QtCore.QRegExp.escape(self._worker.getUser().name)), QtCore.Qt.CaseInsensitive)) == 0) alert = self.getSetting("alerts", "notify_ping") if alertIsDirectPing else self._matchesAlert(message.body) maximumImageWidth = int(view.size().width() * 0.4) # 40% of viewport renderer = MessageRenderer( self._worker.getApiToken(), maximumImageWidth, room, message, live=live, updateRoom=updateRoom, showTimestamps = self.getSetting("display", "show_message_timestamps"), alert=alert, alertIsDirectPing=alertIsDirectPing, parent=self ) if renderer.needsThread(): self.connect(renderer, QtCore.SIGNAL("render(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._renderMessage) renderer.start() else: self._renderMessage(renderer.render(), room, message, live=live, updateRoom=updateRoom, alert=alert, alertIsDirectPing=alertIsDirectPing) def _renderMessage(self, html, room, message, live=True, updateRoom=True, alert=False, alertIsDirectPing=False): if (not room.id in self._rooms): return frame = self._rooms[room.id]["frame"] view = self._rooms[room.id]["view"] if not frame or not view: return if not self.getSetting("display", "show_join_message") and (message.is_joining() or message.is_leaving() or message.is_kick()): return if html: currentScrollbarValue = frame.scrollPosition() autoScroll = (currentScrollbarValue == frame.scrollBarMaximum(QtCore.Qt.Vertical)) frame.setHtml(frame.toHtml() + html) view.show() if autoScroll: frame.scroll(0, frame.scrollBarMaximum(QtCore.Qt.Vertical)) else: frame.scroll(currentScrollbarValue.x(), currentScrollbarValue.y()) tabIndex = self._rooms[room.id]["tab"] tabBar = self._tabs.tabBar() isActiveTab = (self.isActiveWindow() and tabIndex == self._tabs.currentIndex()) if message.is_text() and not isActiveTab: self._rooms[room.id]["newMessages"] += 1 if self._rooms[room.id]["newMessages"] > 0: tabBar.setTabText(tabIndex, unicode("{room} ({count})".format(room = room.name, count = self._rooms[room.id]["newMessages"]))) if not isActiveTab and (alert or self._rooms[room.id]["newMessages"] > 0) and tabBar.tabTextColor(tabIndex) == self.COLORS["normal"]: tabBar.setTabTextColor(tabIndex, self.COLORS["alert" if alert else "new"]) notifyInactiveTab = self.getSetting("alerts", "notify_inactive_tab") if (not isActiveTab and (alert or notifyInactiveTab)) and self.getSetting("alerts", "notify_blink"): self._trayIcon.alert() if live and ((alert or (not isActiveTab and notifyInactiveTab and message.is_text())) and self.getSetting("alerts", "notify_notify")): self._notify(room, unicode("{} says: {}".format(message.user.name, message.body)), message.user) if updateRoom: if (message.is_joining() or message.is_leaving()): self.updateRoomUsers(room.id) elif message.is_upload(): self.updateRoomUploads(room.id) elif message.is_topic_change() and not message.is_by_current_user(): self._cfTopicChanged(room, message.body) # Respond to direct pings while being away, but only send an auto-response if last one was sent more than 2 minutes ago if live and alertIsDirectPing and self.getSetting("program", "away") and self._idle: if self._lastIdleAnswer is None or time.time() - self._lastIdleAnswer >= (int(self.getSetting("program", "away_time_between_messages")) * 60): self._lastIdleAnswer = time.time() self._getWorker().speak(room, unicode("{user}: {message}".format( user = message.user.name, message = self.getSetting("program", "away_message") ))) def _matchesAlert(self, message): matches = False searchMatches = self.getSettings("matches") for match in searchMatches: regex = "\\b{word}\\b".format(word=QtCore.QRegExp.escape(match['match'])) if not match['regex'] else match['match'] if QtCore.QString(message).contains(QtCore.QRegExp(regex, QtCore.Qt.CaseInsensitive)): matches = True break return matches def _cfConnected(self, user, rooms): self._connecting = False self._connected = True self._rooms = {} self._toolBar["rooms"].clear() for room in rooms: self._toolBar["rooms"].addItem(room["name"], room) self.statusBar().showMessage(unicode(self._("{user} connected to Campfire").format(user=user.name)), 5000) self._updateLayout() if not self._pingTimer: self._pingTimer = QtCore.QTimer(self) self.connect(self._pingTimer, QtCore.SIGNAL("timeout()"), self.ping) self._pingTimer.start(60000) # Ping every minute if self.getSetting("program", "away"): self._setUpIdleTracker() if self.getSetting("connection", "join"): rooms = self.getSetting("connection", "rooms") if rooms: for roomId in rooms.split(","): count = self._toolBar["rooms"].count() if count: roomIndex = None for i in range(count): data = self._toolBar["rooms"].itemData(i) if not data.isNull(): data = data.toMap() for key in data: if str(key) == "id" and str(data[key].toString()) == roomId: roomIndex = i break; if roomIndex is not None: break if roomIndex is not None: self.joinRoom(roomIndex) def _cfDisconnected(self): if self._pingTimer: self._pingTimer.stop() self._pingTimer = None if self._idleTimer: self._setUpIdleTracker(False) self._connecting = False self._connected = False self._rooms = {} self._worker = None self.statusBar().clearMessage() def _cfRoomJoined(self, room, messages=[], rejoined=False): if room.id not in self._rooms: return if not rejoined: self._rooms[room.id].update(self._setupRoomUI(room)) self._rooms[room.id]["room"] = room self._rooms[room.id]["stream"] = self._worker.getStream(room) self.updateRoomUsers(room.id) self.updateRoomUploads(room.id) if not rejoined: self.statusBar().showMessage(unicode(self._("Joined room {room}").format(room=room.name)), 5000) self._updatedRoomsList() if not rejoined and messages: for message in messages: self._cfStreamMessage(room, message, live=False, updateRoom=False) def _cfSpoke(self, room, message): self._cfStreamMessage(room, message, live=False) self.statusBar().clearMessage() def _cfRoomLeft(self, room): if self._rooms[room.id]["stream"]: self._rooms[room.id]["stream"].stop().join() if self._rooms[room.id]["upload"]: self._rooms[room.id]["upload"].stop().join() self._tabs.removeTab(self._rooms[room.id]["tab"]) del self._rooms[room.id] self.statusBar().showMessage(unicode(self._("Left room {room}").format(room=room.name)), 5000) self._updatedRoomsList() def _cfRoomUsers(self, room, users, pinging=False): # We may be disconnecting while still processing the list if not room.id in self._rooms: return if not pinging: self.statusBar().clearMessage() self._rooms[room.id]["usersList"].clear() for user in users: item = QtGui.QListWidgetItem(user["name"]) item.setData(QtCore.Qt.UserRole, user) self._rooms[room.id]["usersList"].addItem(item) def _cfRoomUploads(self, room, uploads): # We may be disconnecting while still processing the list if not room.id in self._rooms: return self.statusBar().clearMessage() label = self._rooms[room.id]["filesLabel"] if uploads: html = "" for upload in uploads: html += "{br}• <a href=\"{url}\">{name}</a>".format( br = "<br />" if html else "", url = upload["full_url"], name = upload["name"] ) html = unicode("{text}<br />{html}".format( text = self._("Latest uploads:"), html = html )) label.setText(html) if not label.isVisible(): label.show() elif label.isVisible(): label.setText("") label.hide() def _cfUploadProgress(self, room, current, total): if not room.id in self._rooms: return progressBar = self._rooms[room.id]["uploadProgressBar"] if not self._rooms[room.id]["uploadWidget"].isVisible(): self._rooms[room.id]["uploadWidget"].show() progressBar.setMaximum(total) progressBar.setValue(current) def _cfUploadFinished(self, room): if not room.id in self._rooms: return self._rooms[room.id]["upload"].join() self._rooms[room.id]["upload"] = None self._rooms[room.id]["uploadWidget"].hide() def _cfTopicChanged(self, room, topic): if not room.id in self._rooms: return self._rooms[room.id]["topicLabel"].setText(topic) self.statusBar().clearMessage() def _cfConnectError(self, error): self._cfDisconnected() self._updateLayout() self._cfError(error) def _cfError(self, error): self.statusBar().clearMessage() if not self._connected: QtGui.QMessageBox.critical(self, "Error", self._("Error while connecting: {error}".format(error = str(error)))) else: QtGui.QMessageBox.critical(self, "Error", str(error)) def _cfRoomError(self, error, room): self.statusBar().clearMessage() if isinstance(error, RuntimeError): (code, message) = error if code == 401: self.statusBar().showMessage(unicode(self._("Disconnected from room. Rejoining room {room}...").format(room=room.name)), 5000) self._rooms[room.id]["stream"].stop().join() self._getWorker().join(room.id, True) return QtGui.QMessageBox.critical(self, "Error", str(error)) def _roomSelected(self, index): self._updatedRoomsList(index) def _upload(self, room, path): self._rooms[room.id]["upload"] = self._worker.upload(room, path) self._updateRoomLayout() def _roomTabClose(self, tabIndex): for roomId in self._rooms: if self._rooms[roomId]["tab"] == tabIndex: self.leaveRoom(roomId) break def _roomTabFocused(self): tabIndex = self._tabs.currentIndex() if tabIndex < 0 or not self.isActiveWindow(): return room = self._roomInTabIndex(tabIndex) if not room: return tabBar = self._tabs.tabBar() if self._rooms[room.id]["newMessages"] > 0: self._rooms[room.id]["newMessages"] = 0 tabBar.setTabText(tabIndex, room.name) if tabBar.tabTextColor(tabIndex) != self.COLORS["normal"]: tabBar.setTabTextColor(tabIndex, self.COLORS["normal"]) self._updateRoomLayout() def _roomInTabIndex(self, index): room = None for key in self._rooms: if self._rooms[key]["tab"] == index: room = self._rooms[key]["room"] break return room def _roomInIndex(self, index): room = {} data = self._toolBar["rooms"].itemData(index) if not data.isNull(): data = data.toMap() for key in data: room[str(key)] = unicode(data[key].toString()) return room def _connectWorkerSignals(self, worker): self.connect(worker, QtCore.SIGNAL("error(PyQt_PyObject)"), self._cfError) self.connect(worker, QtCore.SIGNAL("connected(PyQt_PyObject, PyQt_PyObject)"), self._cfConnected) self.connect(worker, QtCore.SIGNAL("connectError(PyQt_PyObject)"), self._cfConnectError) self.connect(worker, QtCore.SIGNAL("streamError(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomError) self.connect(worker, QtCore.SIGNAL("uploadError(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomError) self.connect(worker, QtCore.SIGNAL("joined(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfRoomJoined) self.connect(worker, QtCore.SIGNAL("spoke(PyQt_PyObject, PyQt_PyObject)"), self._cfSpoke) self.connect(worker, QtCore.SIGNAL("streamMessage(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfStreamMessage) self.connect(worker, QtCore.SIGNAL("left(PyQt_PyObject)"), self._cfRoomLeft) self.connect(worker, QtCore.SIGNAL("users(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUsers) self.connect(worker, QtCore.SIGNAL("uploads(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUploads) self.connect(worker, QtCore.SIGNAL("uploadProgress(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfUploadProgress) self.connect(worker, QtCore.SIGNAL("uploadFinished(PyQt_PyObject)"), self._cfUploadFinished) self.connect(worker, QtCore.SIGNAL("topicChanged(PyQt_PyObject, PyQt_PyObject)"), self._cfTopicChanged) def _getWorker(self): if not hasattr(self, "_workers"): self._workers = [] if self._workers: for worker in self._workers: if worker.isFinished(): return worker worker = copy.copy(self._worker) self._connectWorkerSignals(worker) self._workers.append(worker) return worker def _updatedRoomsList(self, index=None): if not index: index = self._toolBar["rooms"].currentIndex() room = self._roomInIndex(index) self._toolBar["join"].setEnabled(False) if not room or room["id"] not in self._rooms: self._toolBar["join"].setEnabled(True) centralWidget = self.centralWidget() if not self._tabs.count(): centralWidget.hide() else: centralWidget.show() def _notify(self, room, message, user): raise NotImplementedError("_notify() must be implemented") def _updateRoomLayout(self): room = self.getCurrentRoom() if room: canUpload = not self._rooms[room.id]["upload"] uploadButton = self._rooms[room.id]["uploadButton"] if ( (canUpload and not uploadButton.isEnabled()) or (not canUpload and uploadButton.isEnabled()) ): uploadButton.setEnabled(canUpload) def _updateLayout(self): self._menus["file"]["connect"].setEnabled(not self._connected and self._canConnect and not self._connecting) self._menus["file"]["disconnect"].setEnabled(self._connected) roomsEmpty = self._toolBar["rooms"].count() == 1 and self._toolBar["rooms"].itemData(0).isNull() if not roomsEmpty and (not self._connected or not self._toolBar["rooms"].count()): self._toolBar["rooms"].clear() self._toolBar["rooms"].addItem(self._("No rooms available")) self._toolBar["rooms"].setEnabled(False) elif not roomsEmpty: self._toolBar["rooms"].setEnabled(True) self._toolBar["roomsLabel"].setEnabled(self._toolBar["rooms"].isEnabled()) self._toolBar["join"].setEnabled(self._toolBar["rooms"].isEnabled()) def _setupRoomUI(self, room): topic = room.topic if room.topic else "" topicLabel = ClickableQLabel(topic) topicLabel.setToolTip(self._("Click here to change room's topic")) topicLabel.setWordWrap(True) self.connect(topicLabel, QtCore.SIGNAL("clicked()"), self.changeTopic) view = SnakeFireWebView(self) view.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) frame = view.page().mainFrame() #Send all link clicks to systems web browser view.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks) def linkClicked(url): QtGui.QDesktopServices.openUrl(url) view.connect(view, QtCore.SIGNAL("linkClicked (const QUrl&)"), linkClicked) # Support auto scroll when needed def autoScroll(size): frame.scroll(0, size.height()) frame.connect(frame, QtCore.SIGNAL("contentsSizeChanged (const QSize&)"), autoScroll) usersList = QtGui.QListWidget() filesLabel = QtGui.QLabel("") filesLabel.setOpenExternalLinks(True) filesLabel.setWordWrap(True) filesLabel.hide() uploadButton = QtGui.QPushButton(self._("&Upload new file")) self.connect(uploadButton, QtCore.SIGNAL("clicked()"), self.uploadFile) uploadProgressBar = QtGui.QProgressBar() uploadProgressLabel = QtGui.QLabel(self._("Uploading:")) uploadCancelButton = QtGui.QPushButton(self._("Cancel")) self.connect(uploadCancelButton, QtCore.SIGNAL("clicked()"), self.uploadCancel) uploadLayout = QtGui.QHBoxLayout() uploadLayout.addWidget(uploadProgressLabel) uploadLayout.addWidget(uploadProgressBar) uploadLayout.addWidget(uploadCancelButton) uploadWidget = QtGui.QWidget() uploadWidget.setLayout(uploadLayout) uploadWidget.hide() leftFrameLayout = QtGui.QVBoxLayout() leftFrameLayout.addWidget(topicLabel) leftFrameLayout.addWidget(view) leftFrameLayout.addWidget(uploadWidget) rightFrameLayout = QtGui.QVBoxLayout() rightFrameLayout.addWidget(usersList) rightFrameLayout.addWidget(filesLabel) rightFrameLayout.addWidget(uploadButton) rightFrameLayout.addStretch(1) leftFrame = QtGui.QWidget() leftFrame.setLayout(leftFrameLayout) rightFrame = QtGui.QWidget() rightFrame.setLayout(rightFrameLayout) splitter = QtGui.QSplitter() splitter.addWidget(leftFrame) splitter.addWidget(rightFrame) splitter.setSizes([splitter.size().width() * 0.75, splitter.size().width() * 0.25]) index = self._tabs.addTab(splitter, room.name) self._tabs.setCurrentIndex(index) if not self.COLORS["normal"]: self.COLORS["normal"] = self._tabs.tabBar().tabTextColor(index) else: self._tabs.tabBar().setTabTextColor(index, self.COLORS["normal"]) return { "tab": index, "view": view, "frame": frame, "usersList": usersList, "topicLabel": topicLabel, "filesLabel": filesLabel, "uploadButton": uploadButton, "uploadWidget": uploadWidget, "uploadProgressBar": uploadProgressBar, "uploadProgressLabel": uploadProgressLabel } def _setupUI(self): self.setWindowTitle(self.NAME) self._addMenu() self._addToolbar() self._tabs = QtGui.QTabWidget() self._tabs.setTabsClosable(True) self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused) self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose) self._editor = SpellTextEditor(lang=self.getSetting("program", "spell_language"), mainFrame=self) speakButton = QtGui.QPushButton(self._("&Send")) self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak) grid = QtGui.QGridLayout() grid.setRowStretch(0, 1) grid.addWidget(self._tabs, 0, 0, 1, -1) grid.addWidget(self._editor, 2, 0) grid.addWidget(speakButton, 2, 1) widget = QtGui.QWidget() widget.setLayout(grid) self.setCentralWidget(widget) tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self) self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused) widget.installEventFilter(tabWidgetFocusEventFilter) self.centralWidget().hide() size = self.getSetting("window", "size") if not size: size = QtCore.QSize(640, 480) self.resize(size) position = self.getSetting("window", "position") if not position: screen = QtGui.QDesktopWidget().screenGeometry() position = QtCore.QPoint((screen.width()-size.width())/2, (screen.height()-size.height())/2) self.move(position) self._updateLayout() menu = QtGui.QMenu(self) menu.addAction(self._menus["file"]["connect"]) menu.addAction(self._menus["file"]["disconnect"]) menu.addSeparator() menu.addAction(self._menus["file"]["exit"]) self._trayIcon = Systray(self._icon, self) self._trayIcon.setContextMenu(menu) self._trayIcon.setToolTip(self.DESCRIPTION) def _addMenu(self): self._menus = { "file": { "connect": self._createAction(self._("&Connect"), self.connectNow, icon="connect.png"), "disconnect": self._createAction(self._("&Disconnect"), self.disconnectNow, icon="disconnect.png"), "exit": self._createAction(self._("E&xit"), self.exit) }, "settings": { "alerts": self._createAction(self._("&Alerts..."), self.alerts, icon="alerts.png"), "options": self._createAction(self._("&Options..."), self.options) }, "help": { "about": self._createAction(self._("A&bout"), self.about) } } menu = self.menuBar() file_menu = menu.addMenu(self._("&File")) file_menu.addAction(self._menus["file"]["connect"]) file_menu.addAction(self._menus["file"]["disconnect"]) file_menu.addSeparator() file_menu.addAction(self._menus["file"]["exit"]) settings_menu = menu.addMenu(self._("S&ettings")) settings_menu.addAction(self._menus["settings"]["alerts"]) settings_menu.addSeparator() settings_menu.addAction(self._menus["settings"]["options"]) help_menu = menu.addMenu(self._("&Help")) help_menu.addAction(self._menus["help"]["about"]) def _addToolbar(self): self._toolBar = { "connect": self._menus["file"]["connect"], "disconnect": self._menus["file"]["disconnect"], "roomsLabel": QtGui.QLabel(self._("Rooms:")), "rooms": QtGui.QComboBox(), "join": self._createAction(self._("Join room"), self.joinRoom, icon="join.png"), "alerts": self._menus["settings"]["alerts"] } self.connect(self._toolBar["rooms"], QtCore.SIGNAL("currentIndexChanged(int)"), self._roomSelected) toolBar = self.toolBar() toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) toolBar.addAction(self._toolBar["connect"]) toolBar.addAction(self._toolBar["disconnect"]) toolBar.addSeparator(); toolBar.addWidget(self._toolBar["roomsLabel"]) toolBar.addWidget(self._toolBar["rooms"]) toolBar.addAction(self._toolBar["join"]) toolBar.addSeparator(); toolBar.addAction(self._toolBar["alerts"]) def _createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): """ Create an action """ action = QtGui.QAction(text, self) if icon is not None: if not isinstance(icon, QtGui.QIcon): action.setIcon(QtGui.QIcon(":/icons/{icon}".format(icon=icon))) else: action.setIcon(icon) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, QtCore.SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def _(self, string, module=None): return str(QtCore.QCoreApplication.translate(module or Snakefire.NAME, string))
class Snakefire(object): DOMAIN = "snakefire.org" NAME = "Snakefire" DESCRIPTION = "Snakefire: Campfire Linux Client" VERSION = "1.0.1" ICON = "snakefire.png" COLORS = { "time": "c0c0c0", "alert": "ff0000", "join": "cb81cb", "leave": "cb81cb", "topic": "808080", "upload": "000000", "message": "000000", "nick": "808080", "nickAlert": "ff0000", "nickSelf": "000080", "tabs": { "normal": None, "new": QtGui.QColor(0, 0, 255), "alert": QtGui.QColor(255, 0, 0) } } def __init__(self): self.DESCRIPTION = self._(self.DESCRIPTION) self._worker = None self._settings = {} self._canConnect = False self._cfDisconnected() self._qsettings = QtCore.QSettings() self._icon = QtGui.QIcon(":/icons/%s" % (self.ICON)) self.setWindowIcon(self._icon) self.setAcceptDrops(True) self._setupUI() settings = self.getSettings("connection") self._canConnect = False if settings["subdomain"] and settings["user"] and settings["password"]: self._canConnect = True self._updateLayout() if settings["connect"]: self.connectNow() def _(self, string, module=None): return str( QtCore.QCoreApplication.translate(module or Snakefire.NAME, string)) def showEvent(self, event): if self._trayIcon.isVisible(): if self._trayIcon.isAlerting(): self._trayIcon.stopAlert() return self._trayIcon.show() def dragEnterEvent(self, event): room = self.getCurrentRoom() canUpload = not self._rooms[room.id]["upload"] if room else False if canUpload and self._getDropFile(event): event.acceptProposedAction() def dropEvent(self, event): room = self.getCurrentRoom() path = self._getDropFile(event) if room and path: self._upload(room, path) def _getDropFile(self, event): files = [] urls = event.mimeData().urls() if urls: for url in urls: path = url.path() if path and os.path.exists(path) and os.path.isfile(path): try: handle = open(str(path)) handle.close() files.append(str(path)) except Exception as e: pass if len(files) > 1: files = [] return files[0] if files else None def getSetting(self, group, setting): settings = self.getSettings(group, asString=False) return settings[setting] if setting in settings else None def setSetting(self, group, setting, value): self._qsettings.beginGroup(group) self._qsettings.setValue(setting, value) self._qsettings.endGroup() def getSettings(self, group, asString=True, reload=False): defaults = { "connection": { "subdomain": None, "user": None, "password": None, "ssl": False, "connect": False, "join": False, "rooms": [] }, "program": { "minimize": False } } if reload or not group in self._settings: settings = defaults[group] if group in defaults else {} self._qsettings.beginGroup(group) for setting in self._qsettings.childKeys(): settings[str(setting)] = self._qsettings.value( setting).toPyObject() self._qsettings.endGroup() boolSettings = [] if group == "connection": boolSettings += ["ssl", "connect", "join"] elif group == "program": boolSettings += ["minimize"] for boolSetting in boolSettings: try: settings[boolSetting] = True if ["true", "1"].index( str(settings[boolSetting]).lower()) >= 0 else False except: settings[boolSetting] = False if group == "connection" and settings["subdomain"] and settings[ "user"]: settings["password"] = keyring.get_password( self.NAME, str(settings["subdomain"]) + "_" + str(settings["user"])) self._settings[group] = settings settings = self._settings[group] if asString: for setting in settings: if not isinstance(settings[setting], bool): settings[setting] = str( settings[setting]) if settings[setting] else "" return settings def setSettings(self, group, settings): self._settings[group] = settings self._qsettings.beginGroup(group) for setting in self._settings[group]: if group != "connection" or setting != "password": self._qsettings.setValue(setting, settings[setting]) elif settings["subdomain"] and settings["user"]: keyring.set_password( self.NAME, settings["subdomain"] + "_" + settings["user"], settings[setting]) self._qsettings.endGroup() if group == "connection": self._canConnect = False if settings["subdomain"] and settings["user"] and settings[ "password"]: self._canConnect = True self._updateLayout() def exit(self): self._forceClose = True self.close() def changeEvent(self, event): if self.getSetting("program", "minimize") and event.type( ) == QtCore.QEvent.WindowStateChange and self.isMinimized(): self.hide() event.ignore() else: event.accept() def closeEvent(self, event): if (not hasattr(self, "_forceClose") or not self._forceClose) and self.getSetting( "program", "minimize"): self.hide() event.ignore() else: if self.getSetting("connection", "join"): self.setSetting( "connection", "rooms", ",".join([str(roomId) for roomId in self._rooms.keys()])) self.disconnectNow() if hasattr(self, "_workers") and self._workers: for worker in self._workers: worker.terminate() worker.wait() if hasattr(self, "_worker") and self._worker: self._worker.terminate() self._worker.wait() self.setSetting("window", "size", self.size()) self.setSetting("window", "position", self.pos()) event.accept() def alerts(self): dialog = AlertsDialog(self) dialog.open() def options(self): dialog = OptionsDialog(self) dialog.open() def connectNow(self): if not self._canConnect: return self._connecting = True self.statusBar().showMessage(self._("Connecting with Campfire...")) self._updateLayout() settings = self.getSettings("connection") self._worker = CampfireWorker(settings["subdomain"], settings["user"], settings["password"], settings["ssl"], self) self._connectWorkerSignals(self._worker) self._worker.connect() def disconnectNow(self): self.statusBar().showMessage(self._("Disconnecting from Campfire...")) if self._worker and hasattr(self, "_rooms"): # Using keys() since the dict could be changed (by _cfRoomLeft()) # while iterating on it for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["room"]: self._worker.leave(self._rooms[roomId]["room"], False) self._cfDisconnected() self._updateLayout() def joinRoom(self, roomIndex=None): room = self._roomInIndex( roomIndex if roomIndex else self._toolBar["rooms"].currentIndex()) if not room: return self._toolBar["join"].setEnabled(False) self.statusBar().showMessage( self._("Joining room %s...") % room["name"]) self._rooms[room["id"]] = { "room": None, "stream": None, "upload": None, "tab": None, "editor": None, "usersList": None, "topicLabel": None, "filesLabel": None, "uploadButton": None, "uploadLabel": None, "uploadWidget": None, "newMessages": 0 } self._getWorker().join(room["id"]) def speak(self): message = self._editor.document().toPlainText() room = self.getCurrentRoom() if not room or message.trimmed().isEmpty(): return self.statusBar().showMessage( self._("Sending message to %s...") % room.name) self._getWorker().speak(room, unicode(message)) self._editor.document().clear() def uploadFile(self): room = self.getCurrentRoom() if not room: return path = QtGui.QFileDialog.getOpenFileName( self, self._("Select file to upload")) if path: self._upload(room, str(path)) def uploadCancel(self): room = self.getCurrentRoom() if not room: return if self._rooms[room.id]["upload"]: self._rooms[room.id]["upload"].stop().join() self._rooms[room.id]["upload"] = None self._rooms[room.id]["uploadWidget"].hide() def leaveRoom(self, roomId): if roomId in self._rooms: self.statusBar().showMessage( self._("Leaving room %s...") % self._rooms[roomId]["room"].name) self._getWorker().leave(self._rooms[roomId]["room"]) def changeTopic(self): room = self.getCurrentRoom() if not room: return topic, ok = QtGui.QInputDialog.getText( self, self._("Change topic"), self._("Enter new topic for room %s") % room.name, QtGui.QLineEdit.Normal, room.topic) if ok: self.statusBar().showMessage( self._("Changing topic for room %s...") % room.name) self._getWorker().changeTopic(room, topic) def updateRoomUsers(self, roomId=None): if not roomId: room = self.getCurrentRoom() if room: roomId = room.id if roomId in self._rooms: self.statusBar().showMessage( self._("Getting users in %s...") % self._rooms[roomId]["room"].name) self._getWorker().users(self._rooms[roomId]["room"]) def updateRoomUploads(self, roomId=None): if not roomId: room = self.getCurrentRoom() if room: roomId = room.id if roomId in self._rooms: self.statusBar().showMessage( self._("Getting uploads in %s...") % self._rooms[roomId]["room"].name) self._getWorker().uploads(self._rooms[roomId]["room"]) def getCurrentRoom(self): index = self._tabs.currentIndex() for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["tab"] == index: return self._rooms[roomId]["room"] def _cfStreamMessage(self, room, message, live=True, updateRoom=True): if (not message.user or (live and message.is_text() and message.is_by_current_user()) or not room.id in self._rooms): return user = message.user.name notify = True alert = False if message.is_text() and not message.is_by_current_user(): alert = self._matchesAlert(message.body) html = None if message.is_joining(): html = "<font color=\"#%s\">" % self.COLORS["join"] html += "--> %s joined %s" % (user, room.name) html += "</font>" elif message.is_leaving(): html = "<font color=\"#%s\">" % self.COLORS["leave"] html += "<-- %s has left %s" % (user, room.name) html += "</font>" elif message.is_text(): body = self._plainTextToHTML( message.tweet["tweet"] if message.is_tweet() else message.body) if message.is_tweet(): body = "<a href=\"%s\">%s</a> <a href=\"%s\">tweeted</a>: %s" % ( "http://twitter.com/%s" % message.tweet["user"], message.tweet["user"], message.tweet["url"], body) elif message.is_paste(): body = "<br /><hr /><code>%s</code><hr />" % body else: body = self._autoLink(body) created = QtCore.QDateTime(message.created_at.year, message.created_at.month, message.created_at.day, message.created_at.hour, message.created_at.minute, message.created_at.second) created.setTimeSpec(QtCore.Qt.UTC) createdFormat = "h:mm ap" if created.daysTo(QtCore.QDateTime.currentDateTime()): createdFormat = "MMM d, %s" % createdFormat html = "<font color=\"#%s\">[%s]</font> " % ( self.COLORS["time"], created.toLocalTime().toString(createdFormat)) if alert: html += "<font color=\"#%s\">" % self.COLORS["alert"] else: html += "<font color=\"#%s\">" % self.COLORS["message"] if message.is_by_current_user(): html += "<font color=\"#%s\">" % self.COLORS["nickSelf"] elif alert: html += "<font color=\"#%s\">" % self.COLORS["nickAlert"] else: html += "<font color=\"#%s\">" % self.COLORS["nick"] html += "%s" % ("<strong>%s</strong>" % user if alert else user) html += "</font>: " html += body html += "</font>" elif message.is_upload(): html = "<font color=\"#%s\">" % self.COLORS["upload"] html += "<strong>%s</strong> uploaded <a href=\"%s\">%s</a>" % ( user, message.upload["url"], message.upload["name"]) html += "</font>" elif message.is_topic_change(): html = "<font color=\"#%s\">" % self.COLORS["leave"] html += "%s changed topic to <strong>%s</strong>" % (user, message.body) html += "</font>" if html: html = "%s<br />" % html editor = self._rooms[room.id]["editor"] if not editor: return scrollbar = editor.verticalScrollBar() currentScrollbarValue = scrollbar.value() autoScroll = (currentScrollbarValue == scrollbar.maximum()) editor.moveCursor(QtGui.QTextCursor.End) editor.textCursor().insertHtml(html) if autoScroll: scrollbar.setValue(scrollbar.maximum()) else: scrollbar.setValue(currentScrollbarValue) tabIndex = self._rooms[room.id]["tab"] tabBar = self._tabs.tabBar() isActiveTab = (self.isActiveWindow() and tabIndex == self._tabs.currentIndex()) if message.is_text() and not isActiveTab: self._rooms[room.id]["newMessages"] += 1 if self._rooms[room.id]["newMessages"] > 0: tabBar.setTabText( tabIndex, "%s (%s)" % (room.name, self._rooms[room.id]["newMessages"])) if not isActiveTab and ( alert or self._rooms[room.id]["newMessages"] > 0 ) and tabBar.tabTextColor( tabIndex) == self.COLORS["tabs"]["normal"]: tabBar.setTabTextColor( tabIndex, self.COLORS["tabs"]["alert" if alert else "new"]) if alert: if not isActiveTab: self._trayIcon.alert() if notify: self._notify(room, message.body) if updateRoom: if (message.is_joining() or message.is_leaving()): self.updateRoomUsers(room.id) elif message.is_upload(): self.updateRoomUploads(room.id) elif message.is_topic_change( ) and not message.is_by_current_user(): self._cfTopicChanged(room, message.body) def _matchesAlert(self, message): matches = False regexes = [] words = ["Mariano Iglesias", "Mariano"] for word in words: regexes.append("\\b%s\\b" % word) for regex in regexes: if QtCore.QString(message).contains( QtCore.QRegExp(regex, QtCore.Qt.CaseInsensitive)): matches = True break return matches def _cfConnected(self, user, rooms): self._connecting = False self._connected = True self._rooms = {} self._toolBar["rooms"].clear() for room in rooms: self._toolBar["rooms"].addItem(room["name"], room) self.statusBar().showMessage( self._("%s connected to Campfire") % user.name, 5000) self._updateLayout() if self.getSetting("connection", "join"): rooms = self.getSetting("connection", "rooms") if rooms: for roomId in rooms.split(","): count = self._toolBar["rooms"].count() if count: roomIndex = None for i in range(count): data = self._toolBar["rooms"].itemData(i) if not data.isNull(): data = data.toMap() for key in data: if str(key) == "id" and str( data[key].toString()) == roomId: roomIndex = i break if roomIndex is not None: break if roomIndex is not None: self.joinRoom(roomIndex) def _cfDisconnected(self): self._connecting = False self._connected = False self._rooms = {} self._worker = None self.statusBar().clearMessage() def _cfRoomJoined(self, room, messages=[]): if room.id not in self._rooms: return self._rooms[room.id].update(self._setupRoomUI(room)) self._rooms[room.id]["room"] = room self._rooms[room.id]["stream"] = self._worker.getStream(room) self.updateRoomUsers(room.id) self.updateRoomUploads(room.id) self.statusBar().showMessage( self._("Joined room %s") % room.name, 5000) self._updatedRoomsList() if messages: for message in messages: self._cfStreamMessage(room, message, live=False, updateRoom=False) def _cfSpoke(self, room, message): self._cfStreamMessage(room, message, live=False) self.statusBar().clearMessage() def _cfRoomLeft(self, room): if self._rooms[room.id]["stream"]: self._rooms[room.id]["stream"].stop().join() if self._rooms[room.id]["upload"]: self._rooms[room.id]["upload"].stop().join() self._tabs.removeTab(self._rooms[room.id]["tab"]) del self._rooms[room.id] self.statusBar().showMessage(self._("Left room %s") % room.name, 5000) self._updatedRoomsList() def _cfRoomUsers(self, room, users): # We may be disconnecting while still processing the list if not room.id in self._rooms: return self.statusBar().clearMessage() self._rooms[room.id]["usersList"].clear() for user in users: item = QtGui.QListWidgetItem(user["name"]) item.setData(QtCore.Qt.UserRole, user) self._rooms[room.id]["usersList"].addItem(item) def _cfRoomUploads(self, room, uploads): # We may be disconnecting while still processing the list if not room.id in self._rooms: return self.statusBar().clearMessage() label = self._rooms[room.id]["filesLabel"] if uploads: html = "" for upload in uploads: html += "%s• <a href=\"%s\">%s</a>" % ( "<br />" if html else "", upload["full_url"], upload["name"]) html = "%s<br />%s" % (self._("Latest uploads:"), html) label.setText(html) if not label.isVisible(): label.show() elif label.isVisible(): label.setText("") label.hide() def _cfUploadProgress(self, room, current, total): if not room.id in self._rooms: return progressBar = self._rooms[room.id]["uploadProgressBar"] if not self._rooms[room.id]["uploadWidget"].isVisible(): self._rooms[room.id]["uploadWidget"].show() progressBar.setMaximum(total) progressBar.setValue(current) def _cfUploadFinished(self, room): if not room.id in self._rooms: return self._rooms[room.id]["upload"].join() self._rooms[room.id]["upload"] = None self._rooms[room.id]["uploadWidget"].hide() def _cfTopicChanged(self, room, topic): if not room.id in self._rooms: return self._rooms[room.id]["topicLabel"].setText(topic) self.statusBar().clearMessage() def _cfConnectError(self, error): self._cfDisconnected() self._updateLayout() self._cfError(error) def _cfError(self, error): self.statusBar().clearMessage() QtGui.QMessageBox.critical(self, "Error", str(error)) def _roomSelected(self, index): self._updatedRoomsList(index) def _upload(self, room, path): self._rooms[room.id]["upload"] = self._worker.upload(room, path) self._updateRoomLayout() def _roomTabClose(self, tabIndex): for roomId in self._rooms: if self._rooms[roomId]["tab"] == tabIndex: self.leaveRoom(roomId) break def _roomTabFocused(self): tabIndex = self._tabs.currentIndex() if tabIndex < 0 or not self.isActiveWindow(): return room = self._roomInTabIndex(tabIndex) if not room: return tabBar = self._tabs.tabBar() if self._rooms[room.id]["newMessages"] > 0: self._rooms[room.id]["newMessages"] = 0 tabBar.setTabText(tabIndex, room.name) if tabBar.tabTextColor(tabIndex) != self.COLORS["tabs"]["normal"]: tabBar.setTabTextColor(tabIndex, self.COLORS["tabs"]["normal"]) self._updateRoomLayout() def _roomInTabIndex(self, index): room = None for key in self._rooms: if self._rooms[key]["tab"] == index: room = self._rooms[key]["room"] break return room def _roomInIndex(self, index): room = {} data = self._toolBar["rooms"].itemData(index) if not data.isNull(): data = data.toMap() for key in data: room[str(key)] = str(unicode(data[key].toString(), "utf-8")) return room def _connectWorkerSignals(self, worker): self.connect(worker, QtCore.SIGNAL("error(PyQt_PyObject)"), self._cfError) self.connect(worker, QtCore.SIGNAL("connected(PyQt_PyObject, PyQt_PyObject)"), self._cfConnected) self.connect(worker, QtCore.SIGNAL("connectError(PyQt_PyObject)"), self._cfConnectError) self.connect(worker, QtCore.SIGNAL("joined(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomJoined) self.connect(worker, QtCore.SIGNAL("spoke(PyQt_PyObject, PyQt_PyObject)"), self._cfSpoke) self.connect( worker, QtCore.SIGNAL( "streamMessage(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfStreamMessage) self.connect(worker, QtCore.SIGNAL("left(PyQt_PyObject)"), self._cfRoomLeft) self.connect(worker, QtCore.SIGNAL("users(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUsers) self.connect(worker, QtCore.SIGNAL("uploads(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUploads) self.connect( worker, QtCore.SIGNAL( "uploadProgress(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfUploadProgress) self.connect(worker, QtCore.SIGNAL("uploadFinished(PyQt_PyObject)"), self._cfUploadFinished) self.connect( worker, QtCore.SIGNAL("topicChanged(PyQt_PyObject, PyQt_PyObject)"), self._cfTopicChanged) def _getWorker(self): if not hasattr(self, "_workers"): self._workers = [] if self._workers: for worker in self._workers: if worker.isFinished(): return worker worker = copy.copy(self._worker) self._connectWorkerSignals(worker) self._workers.append(worker) return worker def _updatedRoomsList(self, index=None): if not index: index = self._toolBar["rooms"].currentIndex() room = self._roomInIndex(index) self._toolBar["join"].setEnabled(False) if not room or room["id"] not in self._rooms: self._toolBar["join"].setEnabled(True) centralWidget = self.centralWidget() if not self._tabs.count(): centralWidget.hide() else: centralWidget.show() def _notify(self, room, message): raise NotImplementedError("_notify() must be implemented") def _updateRoomLayout(self): room = self.getCurrentRoom() if room: canUpload = not self._rooms[room.id]["upload"] uploadButton = self._rooms[room.id]["uploadButton"] if ((canUpload and not uploadButton.isEnabled()) or (not canUpload and uploadButton.isEnabled())): uploadButton.setEnabled(canUpload) def _updateLayout(self): self._menus["file"]["connect"].setEnabled(not self._connected and self._canConnect and not self._connecting) self._menus["file"]["disconnect"].setEnabled(self._connected) roomsEmpty = self._toolBar["rooms"].count( ) == 1 and self._toolBar["rooms"].itemData(0).isNull() if not roomsEmpty and (not self._connected or not self._toolBar["rooms"].count()): self._toolBar["rooms"].clear() self._toolBar["rooms"].addItem(self._("No rooms available")) self._toolBar["rooms"].setEnabled(False) elif not roomsEmpty: self._toolBar["rooms"].setEnabled(True) self._toolBar["roomsLabel"].setEnabled( self._toolBar["rooms"].isEnabled()) self._toolBar["join"].setEnabled(self._toolBar["rooms"].isEnabled()) def _setupRoomUI(self, room): topicLabel = ClickableQLabel(room.topic) topicLabel.setToolTip(self._("Click here to change room's topic")) topicLabel.setWordWrap(True) self.connect(topicLabel, QtCore.SIGNAL("clicked()"), self.changeTopic) editor = QtGui.QTextBrowser() editor.setOpenExternalLinks(True) usersList = QtGui.QListWidget() filesLabel = QtGui.QLabel("") filesLabel.setOpenExternalLinks(True) filesLabel.setWordWrap(True) filesLabel.hide() uploadButton = QtGui.QPushButton(self._("&Upload new file")) self.connect(uploadButton, QtCore.SIGNAL("clicked()"), self.uploadFile) uploadProgressBar = QtGui.QProgressBar() uploadProgressLabel = QtGui.QLabel(self._("Uploading:")) uploadCancelButton = QtGui.QPushButton(self._("Cancel")) self.connect(uploadCancelButton, QtCore.SIGNAL("clicked()"), self.uploadCancel) uploadLayout = QtGui.QHBoxLayout() uploadLayout.addWidget(uploadProgressLabel) uploadLayout.addWidget(uploadProgressBar) uploadLayout.addWidget(uploadCancelButton) uploadWidget = QtGui.QWidget() uploadWidget.setLayout(uploadLayout) uploadWidget.hide() leftFrameLayout = QtGui.QVBoxLayout() leftFrameLayout.addWidget(topicLabel) leftFrameLayout.addWidget(editor) leftFrameLayout.addWidget(uploadWidget) rightFrameLayout = QtGui.QVBoxLayout() rightFrameLayout.addWidget(QtGui.QLabel(self._("Users in room:"))) rightFrameLayout.addWidget(usersList) rightFrameLayout.addWidget(filesLabel) rightFrameLayout.addWidget(uploadButton) rightFrameLayout.addStretch(1) leftFrame = QtGui.QWidget() leftFrame.setLayout(leftFrameLayout) rightFrame = QtGui.QWidget() rightFrame.setLayout(rightFrameLayout) splitter = QtGui.QSplitter() splitter.addWidget(leftFrame) splitter.addWidget(rightFrame) splitter.setSizes( [splitter.size().width() * 0.75, splitter.size().width() * 0.25]) index = self._tabs.addTab(splitter, room.name) self._tabs.setCurrentIndex(index) if not self.COLORS["tabs"]["normal"]: self.COLORS["tabs"]["normal"] = self._tabs.tabBar().tabTextColor( index) else: self._tabs.tabBar().setTabTextColor(index, self.COLORS["tabs"]["normal"]) return { "tab": index, "editor": editor, "usersList": usersList, "topicLabel": topicLabel, "filesLabel": filesLabel, "uploadButton": uploadButton, "uploadWidget": uploadWidget, "uploadProgressBar": uploadProgressBar, "uploadProgressLabel": uploadProgressLabel } def _setupUI(self): self.setWindowTitle(self.NAME) self._addMenu() self._addToolbar() self._tabs = QtGui.QTabWidget() self._tabs.setTabsClosable(True) self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused) self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose) self._editor = QtGui.QPlainTextEdit() self._editor.setFixedHeight(self._editor.fontMetrics().height() * 2) self._editor.installEventFilter( SuggesterKeyPressEventFilter(self, Suggester(self._editor))) speakButton = QtGui.QPushButton(self._("&Send")) self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak) grid = QtGui.QGridLayout() grid.setRowStretch(0, 1) grid.addWidget(self._tabs, 0, 0, 1, -1) grid.addWidget(self._editor, 1, 0) grid.addWidget(speakButton, 1, 1) widget = QtGui.QWidget() widget.setLayout(grid) self.setCentralWidget(widget) tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self) self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused) widget.installEventFilter(tabWidgetFocusEventFilter) self.centralWidget().hide() size = self.getSetting("window", "size") if not size: size = QtCore.QSize(640, 480) self.resize(size) position = self.getSetting("window", "position") if not position: screen = QtGui.QDesktopWidget().screenGeometry() position = QtCore.QPoint((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2) self.move(position) self._updateLayout() menu = QtGui.QMenu(self) menu.addAction(self._menus["file"]["connect"]) menu.addAction(self._menus["file"]["disconnect"]) menu.addSeparator() menu.addAction(self._menus["file"]["exit"]) self._trayIcon = Systray(self._icon, self) self._trayIcon.setContextMenu(menu) self._trayIcon.setToolTip(self.DESCRIPTION) def _addMenu(self): self._menus = { "file": { "connect": self._createAction(self._("&Connect"), self.connectNow, icon="connect.png"), "disconnect": self._createAction(self._("&Disconnect"), self.disconnectNow, icon="disconnect.png"), "exit": self._createAction(self._("E&xit"), self.exit) }, "settings": { "alerts": self._createAction(self._("&Alerts..."), self.alerts, icon="alerts.png"), "options": self._createAction(self._("&Options..."), self.options) }, "help": { "about": self._createAction(self._("A&bout")) } } menu = self.menuBar() file_menu = menu.addMenu(self._("&File")) file_menu.addAction(self._menus["file"]["connect"]) file_menu.addAction(self._menus["file"]["disconnect"]) file_menu.addSeparator() file_menu.addAction(self._menus["file"]["exit"]) settings_menu = menu.addMenu(self._("S&ettings")) settings_menu.addAction(self._menus["settings"]["alerts"]) settings_menu.addSeparator() settings_menu.addAction(self._menus["settings"]["options"]) help_menu = menu.addMenu(self._("&Help")) help_menu.addAction(self._menus["help"]["about"]) def _addToolbar(self): self._toolBar = { "connect": self._menus["file"]["connect"], "disconnect": self._menus["file"]["disconnect"], "roomsLabel": QtGui.QLabel(self._("Rooms:")), "rooms": QtGui.QComboBox(), "join": self._createAction(self._("Join room"), self.joinRoom, icon="join.png"), "alerts": self._menus["settings"]["alerts"] } self.connect(self._toolBar["rooms"], QtCore.SIGNAL("currentIndexChanged(int)"), self._roomSelected) toolBar = self.toolBar() toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) toolBar.addAction(self._toolBar["connect"]) toolBar.addAction(self._toolBar["disconnect"]) toolBar.addSeparator() toolBar.addWidget(self._toolBar["roomsLabel"]) toolBar.addWidget(self._toolBar["rooms"]) toolBar.addAction(self._toolBar["join"]) toolBar.addSeparator() toolBar.addAction(self._toolBar["alerts"]) def _createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): """ Create an action """ action = QtGui.QAction(text, self) if icon is not None: if not isinstance(icon, QtGui.QIcon): action.setIcon(QtGui.QIcon(":/icons/%s" % (icon))) else: action.setIcon(icon) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, QtCore.SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def _plainTextToHTML(self, string): return string.replace("<", "<").replace(">", ">").replace("\n", "<br />") def _autoLink(self, string): urlre = re.compile( "(\(?https?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|])(\">|</a>)?" ) urls = urlre.findall(string) cleanUrls = [] for url in urls: if url[1]: continue currentUrl = url[0] if currentUrl[0] == '(' and currentUrl[-1] == ')': currentUrl = currentUrl[1:-1] if currentUrl in cleanUrls: continue cleanUrls.append(currentUrl) string = re.sub( "(?<!(=\"|\">))" + re.escape(currentUrl), "<a href=\"" + currentUrl + "\">" + currentUrl + "</a>", string) return string
class ScudCloud(QtGui.QMainWindow): plugins = True debug = False forceClose = False messages = 0 speller = Speller() def __init__(self, parent=None, settings_path=""): super(ScudCloud, self).__init__(parent) self.setWindowTitle("ScudCloud") self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path("scudcloud.png")) self.settings = QSettings(self.settings_path + "/scudcloud.cfg", QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) self.stackedWidget = QtGui.QStackedWidget() centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.startURL = Resources.SIGNIN_URL if self.identifier is not None: self.startURL = self.domain() self.addWrapper(self.startURL) self.addMenu() self.tray = Systray(self) self.systray(ScudCloud.minimized) self.installEventFilter(self) self.statusBar().showMessage("Loading Slack...") def addWrapper(self, url): webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) webView.page().networkAccessManager().setCache(self.diskCache) webView.load(QtCore.QUrl(url)) webView.show() self.stackedWidget.addWidget(webView) self.stackedWidget.setCurrentWidget(webView) def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # Required by Youtube videos (HTML5 video support only on Qt5) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, self.plugins) # We don't want Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # Enabling Local Storage (now required by Slack) QWebSettings.globalSettings().setAttribute(QWebSettings.LocalStorageEnabled, True) # We need browsing history (required to not limit LocalStorage) QWebSettings.globalSettings().setAttribute(QWebSettings.PrivateBrowsingEnabled, False) # Enabling Cache self.diskCache = QNetworkDiskCache(self) self.diskCache.setCacheDirectory(self.settings_path) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute(QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.debug) def toggleFullScreen(self): if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def toggleMenuBar(self): menu = self.menuBar() state = menu.isHidden() menu.setVisible(state) if state: self.settings.setValue("Menu", "False") else: self.settings.setValue("Menu", "True") def restore(self): geometry = self.settings.value("geometry") if geometry is not None: self.restoreGeometry(geometry) windowState = self.settings.value("windowState") if windowState is not None: self.restoreState(windowState) else: self.setWindowState(QtCore.Qt.WindowMaximized) def systray(self, show=None): if show is None: show = self.settings.value("Systray") == "True" if show: self.tray.show() self.menus["file"]["close"].setEnabled(True) self.settings.setValue("Systray", "True") else: self.tray.setVisible(False) self.menus["file"]["close"].setEnabled(False) self.settings.setValue("Systray", "False") def readZoom(self): default = 1 if self.settings.value("Zoom") is not None: default = float(self.settings.value("Zoom")) return default def setZoom(self, factor=1): if factor > 0: for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) widget.setZoomFactor(factor) self.settings.setValue("Zoom", factor) def zoomIn(self): self.setZoom(self.current().zoomFactor() + 0.1) def zoomOut(self): self.setZoom(self.current().zoomFactor() - 0.1) def zoomReset(self): self.setZoom() def addMenu(self): self.menus = { "file": { "preferences": self.createAction("Preferences", lambda: self.current().preferences()), "systray": self.createAction("Close to Tray", self.systray, None, True), "addTeam": self.createAction("Sign in to Another Team", lambda: self.switchTo(Resources.SIGNIN_URL)), "signout": self.createAction("Signout", lambda: self.current().logout()), "close": self.createAction("Close", self.close, QKeySequence.Close), "exit": self.createAction("Quit", self.exit, QKeySequence.Quit), }, "edit": {}, "view": { "zoomin": self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn), "zoomout": self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut), "reset": self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0), "fullscreen": self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11), "hidemenu": self.createAction("Toggle Menubar", self.toggleMenuBar, QtCore.Qt.Key_F12), }, "help": { "help": self.createAction( "Help and Feedback", lambda: self.current().help(), QKeySequence.HelpContents ), "center": self.createAction("Slack Help Center", lambda: self.current().helpCenter()), "about": self.createAction("About", lambda: self.current().about()), }, } menu = self.menuBar() fileMenu = menu.addMenu("&File") fileMenu.addAction(self.menus["file"]["preferences"]) fileMenu.addAction(self.menus["file"]["systray"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["addTeam"]) fileMenu.addAction(self.menus["file"]["signout"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["close"]) fileMenu.addAction(self.menus["file"]["exit"]) self.editMenu = menu.addMenu("&Edit") self.updateEditMenu() viewMenu = menu.addMenu("&View") viewMenu.addAction(self.menus["view"]["zoomin"]) viewMenu.addAction(self.menus["view"]["zoomout"]) viewMenu.addAction(self.menus["view"]["reset"]) viewMenu.addSeparator() viewMenu.addAction(self.menus["view"]["fullscreen"]) if Unity is None: viewMenu.addAction(self.menus["view"]["hidemenu"]) helpMenu = menu.addMenu("&Help") helpMenu.addAction(self.menus["help"]["help"]) helpMenu.addAction(self.menus["help"]["center"]) helpMenu.addSeparator() helpMenu.addAction(self.menus["help"]["about"]) self.enableMenus(False) showSystray = self.settings.value("Systray") == "True" self.menus["file"]["systray"].setChecked(showSystray) self.menus["file"]["close"].setEnabled(showSystray) # Restore menu visibility visible = self.settings.value("Menu") if visible is not None and visible == "False": menu.setVisible(False) def enableMenus(self, enabled): self.menus["file"]["preferences"].setEnabled(enabled == True) self.menus["file"]["addTeam"].setEnabled(enabled == True) self.menus["file"]["signout"].setEnabled(enabled == True) self.menus["help"]["help"].setEnabled(enabled == True) def updateEditMenu(self): self.editMenu.clear() self.menus["edit"] = { "undo": self.current().pageAction(QtWebKit.QWebPage.Undo), "redo": self.current().pageAction(QtWebKit.QWebPage.Redo), "cut": self.current().pageAction(QtWebKit.QWebPage.Cut), "copy": self.current().pageAction(QtWebKit.QWebPage.Copy), "paste": self.current().pageAction(QtWebKit.QWebPage.Paste), "back": self.current().pageAction(QtWebKit.QWebPage.Back), "forward": self.current().pageAction(QtWebKit.QWebPage.Forward), "reload": self.current().pageAction(QtWebKit.QWebPage.Reload), } self.editMenu.addAction(self.menus["edit"]["undo"]) self.editMenu.addAction(self.menus["edit"]["redo"]) self.editMenu.addSeparator() self.editMenu.addAction(self.menus["edit"]["cut"]) self.editMenu.addAction(self.menus["edit"]["copy"]) self.editMenu.addAction(self.menus["edit"]["paste"]) self.editMenu.addSeparator() self.editMenu.addAction(self.menus["edit"]["back"]) self.editMenu.addAction(self.menus["edit"]["forward"]) self.editMenu.addAction(self.menus["edit"]["reload"]) def createAction(self, text, slot, shortcut=None, checkable=False): action = QtGui.QAction(text, self) action.triggered.connect(slot) if shortcut is not None: action.setShortcut(shortcut) self.addAction(action) if checkable: action.setCheckable(True) return action def domain(self): if self.identifier.endswith(".slack.com"): return self.identifier else: return "https://" + self.identifier + ".slack.com" def current(self): return self.stackedWidget.currentWidget() def teams(self, teams): for t in teams: # If team_icon is not present, it's because team is already connected if "team_icon" in t: self.leftPane.addTeam(t["id"], t["team_name"], t["team_url"], t["team_icon"]["image_44"], t == teams[0]) if len(teams) > 1: self.leftPane.show() def switchTo(self, url): exists = False for i in range(0, self.stackedWidget.count()): if self.stackedWidget.widget(i).url().toString().startswith(url): self.stackedWidget.setCurrentIndex(i) self.quicklist(self.current().listChannels()) self.current().setFocus() self.leftPane.click(i) exists = True break if not exists: self.addWrapper(url) self.updateEditMenu() def eventFilter(self, obj, event): if event.type() == QtCore.QEvent.ActivationChange and self.isActiveWindow(): self.focusInEvent(event) if event.type() == QtCore.QEvent.KeyPress: # Ctrl + <n> modifiers = QtGui.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ControlModifier: if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0) elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1) elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2) elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3) elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4) elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5) elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6) elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7) elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8) # Ctrl + Tab elif event.key() == QtCore.Qt.Key_Tab: self.leftPane.clickNext(1) # Ctrl + BackTab if (modifiers & QtCore.Qt.ControlModifier) and (modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_Backtab: self.leftPane.clickNext(-1) # Ctrl + Shift + <key> if (modifiers & QtCore.Qt.ShiftModifier) and (modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_V: self.current().createSnippet() return QtGui.QMainWindow.eventFilter(self, obj, event) def focusInEvent(self, event): self.launcher.set_property("urgent", False) self.tray.stopAlert() def titleChanged(self): self.setWindowTitle(self.current().title()) def closeEvent(self, event): if not self.forceClose and self.settings.value("Systray") == "True": self.hide() event.ignore() else: self.cookiesjar.save() self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) # Let's save the first team registered as default qUrl = self.stackedWidget.widget(0).url() if self.identifier is None and Resources.MESSAGES_URL_RE.match(qUrl.toString()): self.settings.setValue("Domain", "https://" + qUrl.host()) def show(self): self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.activateWindow() self.setVisible(True) def exit(self): self.forceClose = True self.close() def quicklist(self, channels): if Dbusmenu is not None: if channels is not None: ql = Dbusmenu.Menuitem.new() self.launcher.set_property("quicklist", ql) for c in channels: if c["is_member"]: item = Dbusmenu.Menuitem.new() item.property_set(Dbusmenu.MENUITEM_PROP_LABEL, "#" + c["name"]) item.property_set("id", c["name"]) item.property_set_bool(Dbusmenu.MENUITEM_PROP_VISIBLE, True) item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel) ql.child_append(item) self.launcher.set_property("quicklist", ql) def notify(self, title, message, icon=None): self.notifier.notify(title, message, icon) self.alert() def alert(self): if not self.isActiveWindow(): self.launcher.set_property("urgent", True) self.tray.alert() def count(self): total = 0 for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) messages = widget.highlights if messages == 0: self.leftPane.stopAlert(widget.team()) else: self.leftPane.alert(widget.team()) if messages is not None: total += messages if total > self.messages: self.alert() if 0 == total: self.launcher.set_property("count_visible", False) self.tray.setCounter(0) else: self.tray.setCounter(total) self.launcher.set_property("count", total) self.launcher.set_property("count_visible", True) self.messages = total
class Snakefire(object): DOMAIN = "www.snakefire.org" NAME = "Snakefire" DESCRIPTION = "Snakefire: Campfire Linux Client" VERSION = "1.0.3" ICON = "snakefire.png" MAC_TRAY_ICON = "snakefire-gray.png" COLORS = { "normal": None, "new": QtGui.QColor(0, 0, 255), "alert": QtGui.QColor(255, 0, 0) } def __init__(self): self.DESCRIPTION = self._(self.DESCRIPTION) self._pingTimer = None self._idleTimer = None self._idle = False self._lastIdleAnswer = None self._worker = None self._settings = {} self._canConnect = False self._cfDisconnected() if len(sys.argv) > 1: self._qsettings = QtCore.QSettings( sys.argv[1], QtCore.QSettings.IniFormat if sys.platform.find("win") == 0 else QtCore.QSettings.NativeFormat) else: self._qsettings = QtCore.QSettings(self.NAME, self.NAME) self._icon = QtGui.QIcon(":/icons/{icon}".format(icon=self.ICON)) if platform.system() == "Darwin": self._trayIconIcon = QtGui.QIcon( ":/icons/{icon}".format(icon=self.MAC_TRAY_ICON)) else: self._trayIconIcon = self._icon self.setWindowIcon(self._icon) self.setAcceptDrops(True) self._setupUI() settings = self.getSettings("connection") self._canConnect = False if settings["subdomain"] and settings["user"] and settings["password"]: self._canConnect = True self._updateLayout() if not self._canConnect: self.options() elif settings["connect"]: self.connectNow() def showEvent(self, event): if self._trayIcon.isVisible(): if self._trayIcon.isAlerting(): self._trayIcon.stopAlert() return self._trayIcon.show() def dragEnterEvent(self, event): room = self.getCurrentRoom() canUpload = not self._rooms[room.id]["upload"] if room else False if canUpload and self._getDropFile(event): event.acceptProposedAction() def dropEvent(self, event): room = self.getCurrentRoom() path = self._getDropFile(event) if room and path: self._upload(room, path) def _getDropFile(self, event): files = [] urls = event.mimeData().urls() if urls: for url in urls: path = url.path() if path and os.path.exists(path) and os.path.isfile(path): try: handle = open(str(path)) handle.close() files.append(str(path)) except: pass if len(files) > 1: files = [] return files[0] if files else None def getSetting(self, group, setting): settings = self.getSettings(group, asString=False) return settings[setting] if setting in settings else None def setSetting(self, group, setting, value): self._qsettings.beginGroup(group) self._qsettings.setValue(setting, value) self._qsettings.endGroup() def getSettings(self, group, asString=True, reload=False): try: spell_language = SpellTextEditor.defaultLanguage() except enchant.errors.Error: spell_language = None defaults = { "connection": { "subdomain": None, "user": None, "password": None, "ssl": False, "connect": False, "join": False, "rooms": [] }, "program": { "minimize": False, "spell_language": spell_language, "away": True, "away_time": 10, "away_time_between_messages": 5, "away_message": self._("I am currently away from {name}").format( name=self.NAME) }, "display": { "theme": "default", "size": 100, "show_join_message": True, "show_part_message": True, "show_message_timestamps": True }, "alerts": { "notify_ping": True, "notify_inactive_tab": False, "notify_blink": True, "notify_notify": True }, "matches": [] } if reload or not group in self._settings: settings = defaults[group] if group in defaults else {} if group == "matches": settings = [] size = self._qsettings.beginReadArray("matches") for i in range(size): self._qsettings.setArrayIndex(i) isRegex = False try: isRegex = True if ["true", "1"].index( str(self._qsettings.value( "regex").toPyObject()).lower()) >= 0 else False except: pass settings.append({ 'regex': isRegex, 'match': self._qsettings.value("match").toPyObject() }) self._qsettings.endArray() else: self._qsettings.beginGroup(group) for setting in self._qsettings.childKeys(): settings[str(setting)] = self._qsettings.value( setting).toPyObject() self._qsettings.endGroup() boolSettings = [] if group == "connection": boolSettings += ["ssl", "connect", "join"] elif group == "program": boolSettings += ["away", "minimize"] elif group == "display": boolSettings += [ "show_join_message", "show_part_message", "show_message_timestamps" ] elif group == "alerts": boolSettings += [ "notify_ping", "notify_inactive_tab", "notify_blink", "notify_notify" ] for boolSetting in boolSettings: try: settings[boolSetting] = True if ["true", "1"].index( str(settings[boolSetting]).lower()) >= 0 else False except: settings[boolSetting] = False if group == "connection" and settings[ "subdomain"] and settings["user"]: settings["password"] = keyring.get_password( self.NAME, str(settings["subdomain"]) + "_" + str(settings["user"])) self._settings[group] = settings settings = self._settings[group] if asString: if isinstance(settings, list): for i, row in enumerate(settings): for setting in row: if not isinstance(row[setting], bool): settings[i][setting] = str( row[setting]) if row[setting] else "" else: for setting in settings: if not isinstance(settings[setting], bool): settings[setting] = str( settings[setting]) if settings[setting] else "" return settings def setSettings(self, group, settings): self._settings[group] = settings if group == "matches": self._qsettings.beginWriteArray("matches") for i, setting in enumerate(settings): self._qsettings.setArrayIndex(i) self._qsettings.setValue("regex", setting["regex"]) self._qsettings.setValue("match", setting["match"]) self._qsettings.endArray() else: self._qsettings.beginGroup(group) for setting in self._settings[group]: if group != "connection" or setting != "password": self._qsettings.setValue(setting, settings[setting]) elif settings["subdomain"] and settings["user"]: keyring.set_password( self.NAME, settings["subdomain"] + "_" + settings["user"], settings[setting]) self._qsettings.endGroup() if group == "connection": self._canConnect = False if settings["subdomain"] and settings["user"] and settings[ "password"]: self._canConnect = True self._updateLayout() elif group == "program": if settings["away"] and self._connected: self._setUpIdleTracker() else: self._setUpIdleTracker(False) if self._editor: if settings["spell_language"]: self._editor.enableSpell(settings["spell_language"]) else: self._editor.disableSpell() elif group == "display": for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["view"]: self._rooms[roomId]["view"].updateTheme( settings["theme"], settings["size"]) def exit(self): self._forceClose = True self.close() def changeEvent(self, event): if self.getSetting("program", "minimize") and event.type( ) == QtCore.QEvent.WindowStateChange and self.isMinimized(): self.hide() event.ignore() else: event.accept() def closeEvent(self, event): if (not hasattr(self, "_forceClose") or not self._forceClose) and self.getSetting( "program", "minimize"): self.hide() event.ignore() else: if self.getSetting("connection", "join"): self.setSetting( "connection", "rooms", ",".join([str(roomId) for roomId in self._rooms.keys()])) self.disconnectNow() if hasattr(self, "_workers") and self._workers: for worker in self._workers: worker.terminate() worker.wait() if hasattr(self, "_worker") and self._worker: self._worker.terminate() self._worker.wait() self.setSetting("window", "size", self.size()) self.setSetting("window", "position", self.pos()) event.accept() def alerts(self): dialog = AlertsDialog(self) dialog.open() def options(self): dialog = OptionsDialog(self) dialog.open() def about(self): dialog = AboutDialog(self) dialog.open() def connectNow(self): if not self._canConnect: return self._connecting = True self.statusBar().showMessage(self._("Connecting with Campfire...")) self._updateLayout() settings = self.getSettings("connection") self._worker = CampfireWorker(settings["subdomain"], settings["user"], settings["password"], settings["ssl"], self) self._connectWorkerSignals(self._worker) self._worker.connect() def disconnectNow(self): self.statusBar().showMessage(self._("Disconnecting from Campfire...")) if self._worker and hasattr(self, "_rooms"): # Using keys() since the dict could be changed (by _cfRoomLeft()) # while iterating on it for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["room"]: self._worker.leave(self._rooms[roomId]["room"], False) self._cfDisconnected() self._updateLayout() def joinRoom(self, roomIndex=None): room = self._roomInIndex( roomIndex if roomIndex else self._toolBar["rooms"].currentIndex()) if not room: return self._toolBar["join"].setEnabled(False) self.statusBar().showMessage( unicode( self._("Joining room {room}...").format(room=room["name"]))) self._rooms[room["id"]] = { "room": None, "stream": None, "upload": None, "tab": None, "view": None, "frame": None, "usersList": None, "topicLabel": None, "filesLabel": None, "uploadButton": None, "uploadLabel": None, "uploadWidget": None, "newMessages": 0 } self._getWorker().join(room["id"]) def ping(self): if not self._connected: return for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["room"]: self.updateRoomUsers(roomId, pinging=True) def speak(self): message = self._editor.document().toPlainText() room = self.getCurrentRoom() if not room or message.trimmed().isEmpty(): return self._editor.document().clear() if message[0] == '/': command = QtCore.QString(message) separatorIndex = command.indexOf(QtCore.QRegExp('\\s')) handled = self.command( command.mid(1, separatorIndex - 1), command.mid(separatorIndex + 1 if separatorIndex >= 0 else command.length())) if handled: return self.statusBar().showMessage( unicode( self._("Sending message to {room}...").format(room=room.name))) self._getWorker().speak(room, unicode(message)) def command(self, command, args): if command.compare(QtCore.QString("away"), QtCore.Qt.CaseInsensitive) == 0: self.toggleAway() return True def uploadFile(self): room = self.getCurrentRoom() if not room: return path = QtGui.QFileDialog.getOpenFileName( self, self._("Select file to upload")) if path: self._upload(room, str(path)) def uploadCancel(self): room = self.getCurrentRoom() if not room: return if self._rooms[room.id]["upload"]: self._rooms[room.id]["upload"].stop().join() self._rooms[room.id]["upload"] = None self._rooms[room.id]["uploadWidget"].hide() def leaveRoom(self, roomId): if roomId in self._rooms: self.statusBar().showMessage( unicode( self._("Leaving room {room}...").format( room=self._rooms[roomId]["room"].name))) self._getWorker().leave(self._rooms[roomId]["room"]) def changeTopic(self): room = self.getCurrentRoom() if not room: return topic, ok = QtGui.QInputDialog.getText( self, self._("Change topic"), unicode( self._("Enter new topic for room {room}").format( room=room.name)), QtGui.QLineEdit.Normal, room.topic) if ok: self.statusBar().showMessage( unicode( self._("Changing topic for room {room}...").format( room=room.name))) self._getWorker().changeTopic(room, topic) def updateRoomUsers(self, roomId=None, pinging=False): if not roomId: room = self.getCurrentRoom() if room: roomId = room.id if roomId in self._rooms: if not pinging: self.statusBar().showMessage( unicode( self._("Getting users in {room}...").format( room=self._rooms[roomId]["room"].name))) self._getWorker().users(self._rooms[roomId]["room"], pinging) def updateRoomUploads(self, roomId=None): if not roomId: room = self.getCurrentRoom() if room: roomId = room.id if roomId in self._rooms: self.statusBar().showMessage( unicode( self._("Getting uploads from {room}...").format( room=self._rooms[roomId]["room"].name))) self._getWorker().uploads(self._rooms[roomId]["room"]) def getCurrentRoom(self): index = self._tabs.currentIndex() for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["tab"] == index: return self._rooms[roomId]["room"] def toggleAway(self): self.setAway(False if self._idle else True) def setAway(self, away=True): self._idle = away self.statusBar().showMessage( self._("You are now away") if self._idle else self._('You are now active'), 5000) def onIdle(self): self.setAway(True) def onActive(self): self.setAway(False) def _setUpIdleTracker(self, enable=True): if self._idleTimer: self._idleTimer.stop() self._idleTimer = None if enable and IdleTimer.supported(): self._idleTimer = IdleTimer( self, int(self.getSetting("program", "away_time")) * 60) self.connect(self._idleTimer, QtCore.SIGNAL("idle()"), self.onIdle) self.connect(self._idleTimer, QtCore.SIGNAL("active()"), self.onActive) self._idleTimer.start() def _cfStreamMessage(self, room, message, live=True, updateRoom=True): if (not message.user or (live and message.is_text() and message.is_by_current_user()) or not room.id in self._rooms): return view = self._rooms[room.id]["view"] if not view: return alert = False alertIsDirectPing = False if message.is_text() and not message.is_by_current_user(): alertIsDirectPing = (QtCore.QString(message.body).indexOf( QtCore.QRegExp( "\\s*\\b{name}\\b".format(name=QtCore.QRegExp.escape( self._worker.getUser().name)), QtCore.Qt.CaseInsensitive)) == 0) alert = self.getSetting( "alerts", "notify_ping") if alertIsDirectPing else self._matchesAlert( message.body) maximumImageWidth = int(view.size().width() * 0.4) # 40% of viewport renderer = MessageRenderer(self._worker.getApiToken(), maximumImageWidth, room, message, live=live, updateRoom=updateRoom, showTimestamps=self.getSetting( "display", "show_message_timestamps"), alert=alert, alertIsDirectPing=alertIsDirectPing, parent=self) if renderer.needsThread(): self.connect( renderer, QtCore.SIGNAL( "render(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)" ), self._renderMessage) renderer.start() else: self._renderMessage(renderer.render(), room, message, live=live, updateRoom=updateRoom, alert=alert, alertIsDirectPing=alertIsDirectPing) def _renderMessage(self, html, room, message, live=True, updateRoom=True, alert=False, alertIsDirectPing=False): if (not room.id in self._rooms): return frame = self._rooms[room.id]["frame"] view = self._rooms[room.id]["view"] if not frame or not view: return if not self.getSetting("display", "show_join_message") and ( message.is_joining() or message.is_leaving() or message.is_kick()): return if html: currentScrollbarValue = frame.scrollPosition() autoScroll = (currentScrollbarValue == frame.scrollBarMaximum( QtCore.Qt.Vertical)) frame.setHtml(frame.toHtml() + html) view.show() if autoScroll: frame.scroll(0, frame.scrollBarMaximum(QtCore.Qt.Vertical)) else: frame.scroll(currentScrollbarValue.x(), currentScrollbarValue.y()) tabIndex = self._rooms[room.id]["tab"] tabBar = self._tabs.tabBar() isActiveTab = (self.isActiveWindow() and tabIndex == self._tabs.currentIndex()) if message.is_text() and not isActiveTab: self._rooms[room.id]["newMessages"] += 1 if self._rooms[room.id]["newMessages"] > 0: tabBar.setTabText( tabIndex, unicode("{room} ({count})".format( room=room.name, count=self._rooms[room.id]["newMessages"]))) if not isActiveTab and ( alert or self._rooms[room.id]["newMessages"] > 0 ) and tabBar.tabTextColor(tabIndex) == self.COLORS["normal"]: tabBar.setTabTextColor( tabIndex, self.COLORS["alert" if alert else "new"]) notifyInactiveTab = self.getSetting("alerts", "notify_inactive_tab") if (not isActiveTab and (alert or notifyInactiveTab)) and self.getSetting( "alerts", "notify_blink"): self._trayIcon.alert() if live and ( (alert or (not isActiveTab and notifyInactiveTab and message.is_text())) and self.getSetting("alerts", "notify_notify")): self._notify( room, unicode("{} says: {}".format(message.user.name, message.body)), message.user) if updateRoom: if (message.is_joining() or message.is_leaving()): self.updateRoomUsers(room.id) elif message.is_upload(): self.updateRoomUploads(room.id) elif message.is_topic_change( ) and not message.is_by_current_user(): self._cfTopicChanged(room, message.body) # Respond to direct pings while being away, but only send an auto-response if last one was sent more than 2 minutes ago if live and alertIsDirectPing and self.getSetting( "program", "away") and self._idle: if self._lastIdleAnswer is None or time.time( ) - self._lastIdleAnswer >= (int( self.getSetting("program", "away_time_between_messages")) * 60): self._lastIdleAnswer = time.time() self._getWorker().speak( room, unicode("{user}: {message}".format(user=message.user.name, message=self.getSetting( "program", "away_message")))) def _matchesAlert(self, message): matches = False searchMatches = self.getSettings("matches") for match in searchMatches: regex = "\\b{word}\\b".format(word=QtCore.QRegExp.escape( match['match'])) if not match['regex'] else match['match'] if QtCore.QString(message).contains( QtCore.QRegExp(regex, QtCore.Qt.CaseInsensitive)): matches = True break return matches def _cfConnected(self, user, rooms): self._connecting = False self._connected = True self._rooms = {} self._toolBar["rooms"].clear() for room in rooms: self._toolBar["rooms"].addItem(room["name"], room) self.statusBar().showMessage( unicode( self._("{user} connected to Campfire").format(user=user.name)), 5000) self._updateLayout() if not self._pingTimer: self._pingTimer = QtCore.QTimer(self) self.connect(self._pingTimer, QtCore.SIGNAL("timeout()"), self.ping) self._pingTimer.start(60000) # Ping every minute if self.getSetting("program", "away"): self._setUpIdleTracker() if self.getSetting("connection", "join"): rooms = self.getSetting("connection", "rooms") if rooms: for roomId in rooms.split(","): count = self._toolBar["rooms"].count() if count: roomIndex = None for i in range(count): data = self._toolBar["rooms"].itemData(i) if not data.isNull(): data = data.toMap() for key in data: if str(key) == "id" and str( data[key].toString()) == roomId: roomIndex = i break if roomIndex is not None: break if roomIndex is not None: self.joinRoom(roomIndex) def _cfDisconnected(self): if self._pingTimer: self._pingTimer.stop() self._pingTimer = None if self._idleTimer: self._setUpIdleTracker(False) self._connecting = False self._connected = False self._rooms = {} self._worker = None self.statusBar().clearMessage() def _cfRoomJoined(self, room, messages=[], rejoined=False): if room.id not in self._rooms: return if not rejoined: self._rooms[room.id].update(self._setupRoomUI(room)) self._rooms[room.id]["room"] = room self._rooms[room.id]["stream"] = self._worker.getStream(room) self.updateRoomUsers(room.id) self.updateRoomUploads(room.id) if not rejoined: self.statusBar().showMessage( unicode(self._("Joined room {room}").format(room=room.name)), 5000) self._updatedRoomsList() if not rejoined and messages: for message in messages: self._cfStreamMessage(room, message, live=False, updateRoom=False) def _cfSpoke(self, room, message): self._cfStreamMessage(room, message, live=False) self.statusBar().clearMessage() def _cfRoomLeft(self, room): if self._rooms[room.id]["stream"]: self._rooms[room.id]["stream"].stop().join() if self._rooms[room.id]["upload"]: self._rooms[room.id]["upload"].stop().join() self._tabs.removeTab(self._rooms[room.id]["tab"]) del self._rooms[room.id] self.statusBar().showMessage( unicode(self._("Left room {room}").format(room=room.name)), 5000) self._updatedRoomsList() def _cfRoomUsers(self, room, users, pinging=False): # We may be disconnecting while still processing the list if not room.id in self._rooms: return if not pinging: self.statusBar().clearMessage() self._rooms[room.id]["usersList"].clear() for user in users: item = QtGui.QListWidgetItem(user["name"]) item.setData(QtCore.Qt.UserRole, user) self._rooms[room.id]["usersList"].addItem(item) def _cfRoomUploads(self, room, uploads): # We may be disconnecting while still processing the list if not room.id in self._rooms: return self.statusBar().clearMessage() label = self._rooms[room.id]["filesLabel"] if uploads: html = "" for upload in uploads: html += "{br}• <a href=\"{url}\">{name}</a>".format( br="<br />" if html else "", url=upload["full_url"], name=upload["name"]) html = unicode("{text}<br />{html}".format( text=self._("Latest uploads:"), html=html)) label.setText(html) if not label.isVisible(): label.show() elif label.isVisible(): label.setText("") label.hide() def _cfUploadProgress(self, room, current, total): if not room.id in self._rooms: return progressBar = self._rooms[room.id]["uploadProgressBar"] if not self._rooms[room.id]["uploadWidget"].isVisible(): self._rooms[room.id]["uploadWidget"].show() progressBar.setMaximum(total) progressBar.setValue(current) def _cfUploadFinished(self, room): if not room.id in self._rooms: return self._rooms[room.id]["upload"].join() self._rooms[room.id]["upload"] = None self._rooms[room.id]["uploadWidget"].hide() def _cfTopicChanged(self, room, topic): if not room.id in self._rooms: return self._rooms[room.id]["topicLabel"].setText(topic) self.statusBar().clearMessage() def _cfConnectError(self, error): self._cfDisconnected() self._updateLayout() self._cfError(error) def _cfError(self, error): self.statusBar().clearMessage() if not self._connected: QtGui.QMessageBox.critical( self, "Error", self._("Error while connecting: {error}".format( error=str(error)))) else: QtGui.QMessageBox.critical(self, "Error", str(error)) def _cfRoomError(self, error, room): self.statusBar().clearMessage() if isinstance(error, RuntimeError): (code, message) = error if code == 401: self.statusBar().showMessage( unicode( self._( "Disconnected from room. Rejoining room {room}..." ).format(room=room.name)), 5000) self._rooms[room.id]["stream"].stop().join() self._getWorker().join(room.id, True) return QtGui.QMessageBox.critical(self, "Error", str(error)) def _roomSelected(self, index): self._updatedRoomsList(index) def _upload(self, room, path): self._rooms[room.id]["upload"] = self._worker.upload(room, path) self._updateRoomLayout() def _roomTabClose(self, tabIndex): for roomId in self._rooms: if self._rooms[roomId]["tab"] == tabIndex: self.leaveRoom(roomId) break def _roomTabFocused(self): tabIndex = self._tabs.currentIndex() if tabIndex < 0 or not self.isActiveWindow(): return room = self._roomInTabIndex(tabIndex) if not room: return tabBar = self._tabs.tabBar() if self._rooms[room.id]["newMessages"] > 0: self._rooms[room.id]["newMessages"] = 0 tabBar.setTabText(tabIndex, room.name) if tabBar.tabTextColor(tabIndex) != self.COLORS["normal"]: tabBar.setTabTextColor(tabIndex, self.COLORS["normal"]) self._updateRoomLayout() def _roomInTabIndex(self, index): room = None for key in self._rooms: if self._rooms[key]["tab"] == index: room = self._rooms[key]["room"] break return room def _roomInIndex(self, index): room = {} data = self._toolBar["rooms"].itemData(index) if not data.isNull(): data = data.toMap() for key in data: room[str(key)] = unicode(data[key].toString()) return room def _connectWorkerSignals(self, worker): self.connect(worker, QtCore.SIGNAL("error(PyQt_PyObject)"), self._cfError) self.connect(worker, QtCore.SIGNAL("connected(PyQt_PyObject, PyQt_PyObject)"), self._cfConnected) self.connect(worker, QtCore.SIGNAL("connectError(PyQt_PyObject)"), self._cfConnectError) self.connect( worker, QtCore.SIGNAL("streamError(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomError) self.connect( worker, QtCore.SIGNAL("uploadError(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomError) self.connect( worker, QtCore.SIGNAL( "joined(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfRoomJoined) self.connect(worker, QtCore.SIGNAL("spoke(PyQt_PyObject, PyQt_PyObject)"), self._cfSpoke) self.connect( worker, QtCore.SIGNAL( "streamMessage(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfStreamMessage) self.connect(worker, QtCore.SIGNAL("left(PyQt_PyObject)"), self._cfRoomLeft) self.connect( worker, QtCore.SIGNAL( "users(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUsers) self.connect(worker, QtCore.SIGNAL("uploads(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUploads) self.connect( worker, QtCore.SIGNAL( "uploadProgress(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfUploadProgress) self.connect(worker, QtCore.SIGNAL("uploadFinished(PyQt_PyObject)"), self._cfUploadFinished) self.connect( worker, QtCore.SIGNAL("topicChanged(PyQt_PyObject, PyQt_PyObject)"), self._cfTopicChanged) def _getWorker(self): if not hasattr(self, "_workers"): self._workers = [] if self._workers: for worker in self._workers: if worker.isFinished(): return worker worker = copy.copy(self._worker) self._connectWorkerSignals(worker) self._workers.append(worker) return worker def _updatedRoomsList(self, index=None): if not index: index = self._toolBar["rooms"].currentIndex() room = self._roomInIndex(index) self._toolBar["join"].setEnabled(False) if not room or room["id"] not in self._rooms: self._toolBar["join"].setEnabled(True) centralWidget = self.centralWidget() if not self._tabs.count(): centralWidget.hide() else: centralWidget.show() def _notify(self, room, message, user): raise NotImplementedError("_notify() must be implemented") def _updateRoomLayout(self): room = self.getCurrentRoom() if room: canUpload = not self._rooms[room.id]["upload"] uploadButton = self._rooms[room.id]["uploadButton"] if ((canUpload and not uploadButton.isEnabled()) or (not canUpload and uploadButton.isEnabled())): uploadButton.setEnabled(canUpload) def _updateLayout(self): self._menus["file"]["connect"].setEnabled(not self._connected and self._canConnect and not self._connecting) self._menus["file"]["disconnect"].setEnabled(self._connected) roomsEmpty = self._toolBar["rooms"].count( ) == 1 and self._toolBar["rooms"].itemData(0).isNull() if not roomsEmpty and (not self._connected or not self._toolBar["rooms"].count()): self._toolBar["rooms"].clear() self._toolBar["rooms"].addItem(self._("No rooms available")) self._toolBar["rooms"].setEnabled(False) elif not roomsEmpty: self._toolBar["rooms"].setEnabled(True) self._toolBar["roomsLabel"].setEnabled( self._toolBar["rooms"].isEnabled()) self._toolBar["join"].setEnabled(self._toolBar["rooms"].isEnabled()) def _setupRoomUI(self, room): topic = room.topic if room.topic else "" topicLabel = ClickableQLabel(topic) topicLabel.setToolTip(self._("Click here to change room's topic")) topicLabel.setWordWrap(True) self.connect(topicLabel, QtCore.SIGNAL("clicked()"), self.changeTopic) view = SnakeFireWebView(self) view.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) frame = view.page().mainFrame() #Send all link clicks to systems web browser view.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks) def linkClicked(url): QtGui.QDesktopServices.openUrl(url) view.connect(view, QtCore.SIGNAL("linkClicked (const QUrl&)"), linkClicked) # Support auto scroll when needed def autoScroll(size): frame.scroll(0, size.height()) frame.connect(frame, QtCore.SIGNAL("contentsSizeChanged (const QSize&)"), autoScroll) usersList = QtGui.QListWidget() filesLabel = QtGui.QLabel("") filesLabel.setOpenExternalLinks(True) filesLabel.setWordWrap(True) filesLabel.hide() uploadButton = QtGui.QPushButton(self._("&Upload new file")) self.connect(uploadButton, QtCore.SIGNAL("clicked()"), self.uploadFile) uploadProgressBar = QtGui.QProgressBar() uploadProgressLabel = QtGui.QLabel(self._("Uploading:")) uploadCancelButton = QtGui.QPushButton(self._("Cancel")) self.connect(uploadCancelButton, QtCore.SIGNAL("clicked()"), self.uploadCancel) uploadLayout = QtGui.QHBoxLayout() uploadLayout.addWidget(uploadProgressLabel) uploadLayout.addWidget(uploadProgressBar) uploadLayout.addWidget(uploadCancelButton) uploadWidget = QtGui.QWidget() uploadWidget.setLayout(uploadLayout) uploadWidget.hide() leftFrameLayout = QtGui.QVBoxLayout() leftFrameLayout.addWidget(topicLabel) leftFrameLayout.addWidget(view) leftFrameLayout.addWidget(uploadWidget) rightFrameLayout = QtGui.QVBoxLayout() rightFrameLayout.addWidget(usersList) rightFrameLayout.addWidget(filesLabel) rightFrameLayout.addWidget(uploadButton) rightFrameLayout.addStretch(1) leftFrame = QtGui.QWidget() leftFrame.setLayout(leftFrameLayout) rightFrame = QtGui.QWidget() rightFrame.setLayout(rightFrameLayout) splitter = QtGui.QSplitter() splitter.addWidget(leftFrame) splitter.addWidget(rightFrame) splitter.setSizes( [splitter.size().width() * 0.75, splitter.size().width() * 0.25]) index = self._tabs.addTab(splitter, room.name) self._tabs.setCurrentIndex(index) if not self.COLORS["normal"]: self.COLORS["normal"] = self._tabs.tabBar().tabTextColor(index) else: self._tabs.tabBar().setTabTextColor(index, self.COLORS["normal"]) return { "tab": index, "view": view, "frame": frame, "usersList": usersList, "topicLabel": topicLabel, "filesLabel": filesLabel, "uploadButton": uploadButton, "uploadWidget": uploadWidget, "uploadProgressBar": uploadProgressBar, "uploadProgressLabel": uploadProgressLabel } def _setupUI(self): self.setWindowTitle(self.NAME) self._addMenu() self._addToolbar() self._tabs = QtGui.QTabWidget() self._tabs.setTabsClosable(True) self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused) self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose) self._editor = SpellTextEditor(lang=self.getSetting( "program", "spell_language"), mainFrame=self) speakButton = QtGui.QPushButton(self._("&Send")) self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak) grid = QtGui.QGridLayout() grid.setRowStretch(0, 1) grid.addWidget(self._tabs, 0, 0, 1, -1) grid.addWidget(self._editor, 2, 0) grid.addWidget(speakButton, 2, 1) widget = QtGui.QWidget() widget.setLayout(grid) self.setCentralWidget(widget) tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self) self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused) widget.installEventFilter(tabWidgetFocusEventFilter) self.centralWidget().hide() size = self.getSetting("window", "size") if not size: size = QtCore.QSize(640, 480) self.resize(size) position = self.getSetting("window", "position") if not position: screen = QtGui.QDesktopWidget().screenGeometry() position = QtCore.QPoint((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2) self.move(position) self._updateLayout() menu = QtGui.QMenu(self) menu.addAction(self._menus["file"]["connect"]) menu.addAction(self._menus["file"]["disconnect"]) menu.addSeparator() menu.addAction(self._menus["file"]["exit"]) self._trayIcon = Systray(self._trayIconIcon, self) self._trayIcon.setContextMenu(menu) self._trayIcon.setToolTip(self.DESCRIPTION) def _addMenu(self): self._menus = { "file": { "connect": self._createAction(self._("&Connect"), self.connectNow, icon="connect.png"), "disconnect": self._createAction(self._("&Disconnect"), self.disconnectNow, icon="disconnect.png"), "exit": self._createAction(self._("E&xit"), self.exit) }, "settings": { "alerts": self._createAction(self._("&Alerts..."), self.alerts, icon="alerts.png"), "options": self._createAction(self._("&Options..."), self.options) }, "help": { "about": self._createAction(self._("A&bout"), self.about) } } menu = self.menuBar() file_menu = menu.addMenu(self._("&File")) file_menu.addAction(self._menus["file"]["connect"]) file_menu.addAction(self._menus["file"]["disconnect"]) file_menu.addSeparator() file_menu.addAction(self._menus["file"]["exit"]) settings_menu = menu.addMenu(self._("S&ettings")) settings_menu.addAction(self._menus["settings"]["alerts"]) settings_menu.addSeparator() settings_menu.addAction(self._menus["settings"]["options"]) help_menu = menu.addMenu(self._("&Help")) help_menu.addAction(self._menus["help"]["about"]) def _addToolbar(self): self._toolBar = { "connect": self._menus["file"]["connect"], "disconnect": self._menus["file"]["disconnect"], "roomsLabel": QtGui.QLabel(self._("Rooms:")), "rooms": QtGui.QComboBox(), "join": self._createAction(self._("Join room"), self.joinRoom, icon="join.png"), "alerts": self._menus["settings"]["alerts"] } self.connect(self._toolBar["rooms"], QtCore.SIGNAL("currentIndexChanged(int)"), self._roomSelected) toolBar = self.toolBar() toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) toolBar.addAction(self._toolBar["connect"]) toolBar.addAction(self._toolBar["disconnect"]) toolBar.addSeparator() toolBar.addWidget(self._toolBar["roomsLabel"]) toolBar.addWidget(self._toolBar["rooms"]) toolBar.addAction(self._toolBar["join"]) toolBar.addSeparator() toolBar.addAction(self._toolBar["alerts"]) def _createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): """ Create an action """ action = QtGui.QAction(text, self) if icon is not None: if not isinstance(icon, QtGui.QIcon): action.setIcon(QtGui.QIcon(":/icons/{icon}".format(icon=icon))) else: action.setIcon(icon) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, QtCore.SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def _(self, string, module=None): return str( QtCore.QCoreApplication.translate(module or Snakefire.NAME, string))
def _setupUI(self): self.setWindowTitle(self.NAME) self._addMenu() self._addToolbar() self._tabs = QtGui.QTabWidget() self._tabs.setTabsClosable(True) self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused) self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose) self._editor = SpellTextEditor(lang=self.getSetting( "program", "spell_language"), mainFrame=self) speakButton = QtGui.QPushButton(self._("&Send")) self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak) grid = QtGui.QGridLayout() grid.setRowStretch(0, 1) grid.addWidget(self._tabs, 0, 0, 1, -1) grid.addWidget(self._editor, 2, 0) grid.addWidget(speakButton, 2, 1) widget = QtGui.QWidget() widget.setLayout(grid) self.setCentralWidget(widget) tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self) self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused) widget.installEventFilter(tabWidgetFocusEventFilter) self.centralWidget().hide() size = self.getSetting("window", "size") if not size: size = QtCore.QSize(640, 480) self.resize(size) position = self.getSetting("window", "position") if not position: screen = QtGui.QDesktopWidget().screenGeometry() position = QtCore.QPoint((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2) self.move(position) self._updateLayout() menu = QtGui.QMenu(self) menu.addAction(self._menus["file"]["connect"]) menu.addAction(self._menus["file"]["disconnect"]) menu.addSeparator() menu.addAction(self._menus["file"]["exit"]) self._trayIcon = Systray(self._trayIconIcon, self) self._trayIcon.setContextMenu(menu) self._trayIcon.setToolTip(self.DESCRIPTION)
class zcswebapp(QtGui.QMainWindow): plugins = True debug = False forceClose = False messages = 0 def __init__(self, parent = None, settings_path = ""): super(zcswebapp, self).__init__(parent) self.setWindowTitle('zcswebapp') self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('zcswebapp.png')) self.settings = QSettings(self.settings_path + '/zcswebapp.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id("zcswebapp.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) self.stackedWidget = QtGui.QStackedWidget() self.stackedWidget.addWidget(webView) centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.addMenu() self.tray = Systray(self) self.systray(zcswebapp.minimized) self.installEventFilter(self) if self.identifier is None: webView.load(QtCore.QUrl(Resources.SIGNIN_URL)) else: webView.load(QtCore.QUrl(self.domain())) webView.show() def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # Required by Youtube videos (HTML5 video support only on Qt5) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, self.plugins) # We don't want Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # We don't need History QWebSettings.globalSettings().setAttribute(QWebSettings.PrivateBrowsingEnabled, True) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute(QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.debug) def toggleFullScreen(self): if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def restore(self): geometry = self.settings.value("geometry") if geometry is not None: self.restoreGeometry(geometry) windowState = self.settings.value("windowState") if windowState is not None: self.restoreState(windowState) else: self.showMaximized() def systray(self, show=None): if show is None: show = self.settings.value("Systray") == "True" if show: self.tray.show() self.menus["file"]["close"].setEnabled(True) self.settings.setValue("Systray", "True") else: self.tray.setVisible(False) self.menus["file"]["close"].setEnabled(False) self.settings.setValue("Systray", "False") def readZoom(self): default = 1 if self.settings.value("Zoom") is not None: default = float(self.settings.value("Zoom")) return default def setZoom(self, factor=1): if factor > 0: for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) widget.setZoomFactor(factor) self.settings.setValue("Zoom", factor) def zoomIn(self): self.setZoom(self.current().zoomFactor() + 0.1) def zoomOut(self): self.setZoom(self.current().zoomFactor() - 0.1) def zoomReset(self): self.setZoom() def addMenu(self): self.menus = { "file": { "preferences": self.createAction("Preferences", self.current().preferences), "systray": self.createAction("Close to Tray", self.systray, None, True), "addTeam": self.createAction("Sign in to Another Team", self.current().addTeam), "signout": self.createAction("Signout", self.current().logout), "close": self.createAction("Close", self.close, QKeySequence.Close), "exit": self.createAction("Quit", self.exit, QKeySequence.Quit) }, "edit": { "undo": self.current().pageAction(QtWebKit.QWebPage.Undo), "redo": self.current().pageAction(QtWebKit.QWebPage.Redo), "cut": self.current().pageAction(QtWebKit.QWebPage.Cut), "copy": self.current().pageAction(QtWebKit.QWebPage.Copy), "paste": self.current().pageAction(QtWebKit.QWebPage.Paste), "back": self.current().pageAction(QtWebKit.QWebPage.Back), "forward": self.current().pageAction(QtWebKit.QWebPage.Forward), "reload": self.current().pageAction(QtWebKit.QWebPage.Reload) }, "view": { "zoomin": self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn), "zoomout": self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut), "reset": self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0), "fullscreen": self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11) }, "help": { "help": self.createAction("Help and Feedback", self.current().help, QKeySequence.HelpContents), "center": self.createAction("Slack Help Center", self.current().helpCenter), "about": self.createAction("About", self.current().about) } } menu = self.menuBar() fileMenu = menu.addMenu("&File") fileMenu.addAction(self.menus["file"]["preferences"]) fileMenu.addAction(self.menus["file"]["systray"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["addTeam"]) fileMenu.addAction(self.menus["file"]["signout"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["close"]) fileMenu.addAction(self.menus["file"]["exit"]) editMenu = menu.addMenu("&Edit") editMenu.addAction(self.menus["edit"]["undo"]) editMenu.addAction(self.menus["edit"]["redo"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["cut"]) editMenu.addAction(self.menus["edit"]["copy"]) editMenu.addAction(self.menus["edit"]["paste"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["back"]) editMenu.addAction(self.menus["edit"]["forward"]) editMenu.addAction(self.menus["edit"]["reload"]) viewMenu = menu.addMenu("&View") viewMenu.addAction(self.menus["view"]["zoomin"]) viewMenu.addAction(self.menus["view"]["zoomout"]) viewMenu.addAction(self.menus["view"]["reset"]) viewMenu.addSeparator() viewMenu.addAction(self.menus["view"]["fullscreen"]) helpMenu = menu.addMenu("&Help") helpMenu.addAction(self.menus["help"]["help"]) helpMenu.addAction(self.menus["help"]["center"]) helpMenu.addSeparator() helpMenu.addAction(self.menus["help"]["about"]) self.enableMenus(False) showSystray = self.settings.value("Systray") == "True" self.menus["file"]["systray"].setChecked(showSystray) self.menus["file"]["close"].setEnabled(showSystray) def enableMenus(self, enabled): self.menus["file"]["preferences"].setEnabled(enabled == True) self.menus["file"]["addTeam"].setEnabled(enabled == True) self.menus["file"]["signout"].setEnabled(enabled == True) self.menus["help"]["help"].setEnabled(enabled == True) def createAction(self, text, slot, shortcut=None, checkable=False): action = QtGui.QAction(text, self) if shortcut is not None: action.setShortcut(shortcut) action.triggered.connect(slot) if checkable: action.setCheckable(True) return action def domain(self): if self.identifier.endswith(".slack.com"): return self.identifier else: return "https://"+self.identifier+".slack.com" def current(self): return self.stackedWidget.currentWidget() def teams(self, teams): if teams is not None and len(teams) > 1: self.leftPane.show() for t in teams: try: self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], t['team_icon']['image_88'], t == teams[0]) except: self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], '', t == teams[0]) def switchTo(self, url): qUrl = QtCore.QUrl(url) index = -1 for i in range(0, self.stackedWidget.count()): if self.stackedWidget.widget(i).url().toString().startswith(url): index = i break if index != -1: self.stackedWidget.setCurrentIndex(index) else: webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) webView.load(qUrl) webView.show() self.stackedWidget.addWidget(webView) self.stackedWidget.setCurrentWidget(webView) self.quicklist(self.current().listChannels()) self.enableMenus(self.current().isConnected()) # Save the last used team as default self.settings.setValue("Domain", 'https://'+qUrl.host()) def eventFilter(self, obj, event): if event.type() == QtCore.QEvent.ActivationChange and self.isActiveWindow(): self.focusInEvent(event) if event.type() == QtCore.QEvent.KeyPress: # Ctrl + <n> if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier: if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0) elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1) elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2) elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3) elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4) elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5) elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6) elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7) elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8) # Ctrl + Shift + <key> if (QtGui.QApplication.keyboardModifiers() & QtCore.Qt.ShiftModifier) and (QtGui.QApplication.keyboardModifiers() & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_V: self.current().createSnippet() return QtGui.QMainWindow.eventFilter(self, obj, event); def focusInEvent(self, event): self.launcher.set_property("urgent", False) self.tray.stopAlert() def titleChanged(self): self.setWindowTitle(self.current().title()) def closeEvent(self, event): if not self.forceClose and self.settings.value("Systray") == "True": self.hide() event.ignore() else: self.cookiesjar.save() self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) def show(self): self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.activateWindow() self.setVisible(True) def exit(self): self.forceClose = True self.close() def quicklist(self, channels): if Dbusmenu is not None: ql = Dbusmenu.Menuitem.new() self.launcher.set_property("quicklist", ql) if channels is not None: for c in channels: if c['is_member']: item = Dbusmenu.Menuitem.new () item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, "#"+c['name']) item.property_set ("id", c['name']) item.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True) item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel) ql.child_append(item) self.launcher.set_property("quicklist", ql) def notify(self, title, message): self.notifier.notify(title, message) self.alert() def alert(self): if not self.isActiveWindow(): self.launcher.set_property("urgent", True) self.tray.alert() def count(self): total = 0 for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) if widget.messages == 0: self.leftPane.stopAlert(widget.team()) else: self.leftPane.alert(widget.team()) total+=widget.messages if total > self.messages: self.alert() if 0 == total: self.launcher.set_property("count_visible", False) self.tray.setCounter(0) else: self.tray.setCounter(total) self.launcher.set_property("count", total) self.launcher.set_property("count_visible", True) self.messages = total
class Snakefire(object): DOMAIN = "snakefire.org" NAME = "Snakefire" DESCRIPTION = "Snakefire: Campfire Linux Client" VERSION = "1.0.1" ICON = "snakefire.png" COLORS = { "normal": None, "new": QtGui.QColor(0, 0, 255), "alert": QtGui.QColor(255, 0, 0) } MESSAGES = { "alert": '<div class="alert"><span class="time">[{time}]</span> <span class="author">{user}</span>: {message}</div>', "image": '<span class="upload image"><a href="{url}"><img src="data:image/{type};base64,{data}" title="{name}" {attribs} /></a></span>', "join": '<div class="joined">--> {user} joined {room}</div>', "leave": '<div class="left"><-- {user} has left {room}</div>', "message_self": '<div class="message"><span class="time">[{time}]</span> <span class="author self">{user}</span>: {message}</div>', "message": '<div class="message"><span class="time">[{time}]</span> <span class="author">{user}</span>: {message}</div>', "paste": '<div class="paste">{message}</div>', "upload": '<span class="upload"><a href="{url}">{name}</a></span>', "topic": '<div class="topic">{user} changed topic to <span class="new_topic">{topic}</span></div>', "tweet": '<div class="tweet"><a href="{url_user}">{user}</a> <a href="{url}">tweeted</a>: {message}</div>' } def __init__(self): self.DESCRIPTION = self._(self.DESCRIPTION) self._pingTimer = None self._idleTimer = None self._idle = False self._worker = None self._settings = {} self._canConnect = False self._cfDisconnected() self._qsettings = QtCore.QSettings() self._icon = QtGui.QIcon(":/icons/{icon}".format(icon=self.ICON)) self.setWindowIcon(self._icon) self.setAcceptDrops(True) self._setupUI() settings = self.getSettings("connection") self._canConnect = False if settings["subdomain"] and settings["user"] and settings["password"]: self._canConnect = True self._updateLayout() if settings["connect"]: self.connectNow() def showEvent(self, event): if self._trayIcon.isVisible(): if self._trayIcon.isAlerting(): self._trayIcon.stopAlert() return self._trayIcon.show() def dragEnterEvent(self, event): room = self.getCurrentRoom() canUpload = not self._rooms[room.id]["upload"] if room else False if canUpload and self._getDropFile(event): event.acceptProposedAction() def dropEvent(self, event): room = self.getCurrentRoom() path = self._getDropFile(event) if room and path: self._upload(room, path) def _getDropFile(self, event): files = [] urls = event.mimeData().urls() if urls: for url in urls: path = url.path() if path and os.path.exists(path) and os.path.isfile(path): try: handle = open(str(path)) handle.close() files.append(str(path)) except Exception as e: pass if len(files) > 1: files = [] return files[0] if files else None def getSetting(self, group, setting): settings = self.getSettings(group, asString=False); return settings[setting] if setting in settings else None def setSetting(self, group, setting, value): self._qsettings.beginGroup(group); self._qsettings.setValue(setting, value) self._qsettings.endGroup(); def getSettings(self, group, asString=True, reload=False): defaults = { "connection": { "subdomain": None, "user": None, "password": None, "ssl": False, "connect": False, "join": False, "rooms": [] }, "program": { "minimize": False, "away": True, "away_time": 10, "away_message": self._("I am currently away from {name}").format(name=self.NAME) }, "display": { "theme": "default", "size": 100, "show_join_message": True, "show_part_message": True }, "alerts": { "notify_inactive_tab": False, "matches": "Snakefire;python" } } if reload or not group in self._settings: settings = defaults[group] if group in defaults else {} self._qsettings.beginGroup(group); for setting in self._qsettings.childKeys(): settings[str(setting)] = self._qsettings.value(setting).toPyObject() self._qsettings.endGroup(); boolSettings = [] if group == "connection": boolSettings += ["ssl", "connect", "join"] elif group == "program": boolSettings += ["away", "minimize"] elif group == "display": boolSettings += ["show_join_message", "show_part_message"] elif group == "alerts": boolSettings += ["notify_inactive_tab"] for boolSetting in boolSettings: try: settings[boolSetting] = True if ["true", "1"].index(str(settings[boolSetting]).lower()) >= 0 else False except: settings[boolSetting] = False if group == "connection" and settings["subdomain"] and settings["user"]: settings["password"] = keyring.get_password(self.NAME, str(settings["subdomain"])+"_"+str(settings["user"])) self._settings[group] = settings settings = self._settings[group] if asString: for setting in settings: if not isinstance(settings[setting], bool): settings[setting] = str(settings[setting]) if settings[setting] else "" return settings def setSettings(self, group, settings): self._settings[group] = settings; self._qsettings.beginGroup(group); for setting in self._settings[group]: if group != "connection" or setting != "password": self._qsettings.setValue(setting, settings[setting]) elif settings["subdomain"] and settings["user"]: keyring.set_password(self.NAME, settings["subdomain"]+"_"+settings["user"], settings[setting]) self._qsettings.endGroup(); if group == "connection": self._canConnect = False if settings["subdomain"] and settings["user"] and settings["password"]: self._canConnect = True self._updateLayout() elif group == "program": if settings["away"] and self._connected: self._setUpIdleTracker() else: self._setUpIdleTracker(False) elif group == "display": for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["view"]: self._rooms[roomId]["view"].updateTheme(settings["theme"], settings["size"]) def exit(self): self._forceClose = True self.close() def changeEvent(self, event): if self.getSetting("program", "minimize") and event.type() == QtCore.QEvent.WindowStateChange and self.isMinimized(): self.hide() event.ignore() else: event.accept() def closeEvent(self, event): if (not hasattr(self, "_forceClose") or not self._forceClose) and self.getSetting("program", "minimize"): self.hide() event.ignore() else: if self.getSetting("connection", "join"): self.setSetting("connection", "rooms", ",".join([str(roomId) for roomId in self._rooms.keys()])) self.disconnectNow() if hasattr(self, "_workers") and self._workers: for worker in self._workers: worker.terminate() worker.wait() if hasattr(self, "_worker") and self._worker: self._worker.terminate() self._worker.wait() self.setSetting("window", "size", self.size()) self.setSetting("window", "position", self.pos()) event.accept() def alerts(self): dialog = AlertsDialog(self) dialog.open() def options(self): dialog = OptionsDialog(self) dialog.open() def connectNow(self): if not self._canConnect: return self._connecting = True self.statusBar().showMessage(self._("Connecting with Campfire...")) self._updateLayout() settings = self.getSettings("connection") self._worker = CampfireWorker(settings["subdomain"], settings["user"], settings["password"], settings["ssl"], self) self._connectWorkerSignals(self._worker) self._worker.connect() def disconnectNow(self): self.statusBar().showMessage(self._("Disconnecting from Campfire...")) if self._worker and hasattr(self, "_rooms"): # Using keys() since the dict could be changed (by _cfRoomLeft()) # while iterating on it for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["room"]: self._worker.leave(self._rooms[roomId]["room"], False) self._cfDisconnected() self._updateLayout() def joinRoom(self, roomIndex=None): room = self._roomInIndex(roomIndex if roomIndex else self._toolBar["rooms"].currentIndex()) if not room: return self._toolBar["join"].setEnabled(False) self.statusBar().showMessage(self._("Joining room {room}...").format(room=room["name"])) self._rooms[room["id"]] = { "room": None, "stream": None, "upload": None, "tab": None, "view": None, "frame": None, "usersList": None, "topicLabel": None, "filesLabel": None, "uploadButton": None, "uploadLabel": None, "uploadWidget": None, "newMessages": 0 } self._getWorker().join(room["id"]) def ping(self): if not self._connected: return for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["room"]: self.updateRoomUsers(roomId, pinging=True) def speak(self): message = self._editor.document().toPlainText() room = self.getCurrentRoom() if not room or message.trimmed().isEmpty(): return self._editor.document().clear() if message[0] == '/': command = QtCore.QString(message) separatorIndex = command.indexOf(QtCore.QRegExp('\\s')); handled = self.command(command.mid(1, separatorIndex-1), command.mid(separatorIndex + 1 if separatorIndex >= 0 else command.length())) if handled: return self.statusBar().showMessage(self._("Sending message to {room}...").format(room=room.name)) self._getWorker().speak(room, unicode(message)) def command(self, command, args): if command.compare(QtCore.QString("away"), QtCore.Qt.CaseInsensitive) == 0: self.toggleAway() return True def uploadFile(self): room = self.getCurrentRoom() if not room: return path = QtGui.QFileDialog.getOpenFileName(self, self._("Select file to upload")) if path: self._upload(room, str(path)) def uploadCancel(self): room = self.getCurrentRoom() if not room: return if self._rooms[room.id]["upload"]: self._rooms[room.id]["upload"].stop().join() self._rooms[room.id]["upload"] = None self._rooms[room.id]["uploadWidget"].hide() def leaveRoom(self, roomId): if roomId in self._rooms: self.statusBar().showMessage(self._("Leaving room {room}...").format(room=self._rooms[roomId]["room"].name)) self._getWorker().leave(self._rooms[roomId]["room"]) def changeTopic(self): room = self.getCurrentRoom() if not room: return topic, ok = QtGui.QInputDialog.getText(self, self._("Change topic"), self._("Enter new topic for room {room}").format(room=room.name), QtGui.QLineEdit.Normal, room.topic ) if ok: self.statusBar().showMessage(self._("Changing topic for room {room}...").format(room=room.name)) self._getWorker().changeTopic(room, topic) def updateRoomUsers(self, roomId = None, pinging = False): if not roomId: room = self.getCurrentRoom() if room: roomId = room.id if roomId in self._rooms: if not pinging: self.statusBar().showMessage(self._("Getting users in {room}...").format(room=self._rooms[roomId]["room"].name)) self._getWorker().users(self._rooms[roomId]["room"], pinging) def updateRoomUploads(self, roomId = None): if not roomId: room = self.getCurrentRoom() if room: roomId = room.id if roomId in self._rooms: self.statusBar().showMessage(self._("Getting uploads from {room}...").format(room=self._rooms[roomId]["room"].name)) self._getWorker().uploads(self._rooms[roomId]["room"]) def getCurrentRoom(self): index = self._tabs.currentIndex() for roomId in self._rooms.keys(): if roomId in self._rooms and self._rooms[roomId]["tab"] == index: return self._rooms[roomId]["room"] def toggleAway(self): self.setAway(False if self._idle else True) def setAway(self, away=True): self._idle = away self.statusBar().showMessage(self._("You are now away") if self._idle else self._('You are now active'), 5000) def _idle(self): self.setAway(True) def _active(self): self.setAway(False) def _setUpIdleTracker(self, enable=True): if self._idleTimer: self._idleTimer.stop().wait() self._idleTimer = None if enable: try: self._idleTimer = IdleTimer(self, self.getSetting("program", "away_time") * 60) self.connect(self._idleTimer, QtCore.SIGNAL("idle()"), self._idle) self.connect(self._idleTimer, QtCore.SIGNAL("active()"), self._active) self._idleTimer.start() except: self._idleTimer = None def _cfStreamMessage(self, room, message, live=True, updateRoom=True): if ( not message.user or (live and message.is_text() and message.is_by_current_user()) or not room.id in self._rooms ): return view = self._rooms[room.id]["view"] frame = self._rooms[room.id]["frame"] if not view and frame: return notify = True alert = False alertIsDirectPing = False if message.is_text() and not message.is_by_current_user(): alertIsDirectPing = QtCore.QString(message.body).contains(QtCore.QRegExp("\\b{name}\\b".format(name=self._worker.getUser().name), QtCore.Qt.CaseInsensitive)) alert = True if alertIsDirectPing else self._matchesAlert(message.body) html = None if message.is_joining() and self.getSetting("display", "show_join_message"): html = self.MESSAGES["join"].format(user=message.user.name, room=room.name) elif message.is_leaving() and self.getSetting("display", "show_join_message"): html = self.MESSAGES["leave"].format(user=message.user.name, room=room.name) elif message.is_text() or message.is_upload(): if message.body: body = self._plainTextToHTML(message.tweet["tweet"] if message.is_tweet() else message.body) if message.is_tweet(): body = self.MESSAGES["tweet"].format( url_user = "******".format(user=message.tweet["user"]), user = message.tweet["user"], url = message.tweet["url"], message = body ) elif message.is_paste(): body = self.MESSAGES["paste"].format(message=body) elif message.is_upload(): body = self._displayUpload(view, message) else: body = self._autoLink(body) created = QtCore.QDateTime( message.created_at.year, message.created_at.month, message.created_at.day, message.created_at.hour, message.created_at.minute, message.created_at.second ) created.setTimeSpec(QtCore.Qt.UTC) createdFormat = "h:mm ap" if created.daysTo(QtCore.QDateTime.currentDateTime()): createdFormat = "MMM d, {createdFormat}".format(createdFormat=createdFormat) key = "message" if message.is_by_current_user(): key = "message_self" elif alert: key = "alert" html = self.MESSAGES[key].format( time = created.toLocalTime().toString(createdFormat), user = message.user.name, message = body ) elif message.is_topic_change(): html = self.MESSAGES["topic"].format(user=message.user.name, topic=message.body) if html: currentScrollbarValue = frame.scrollPosition() autoScroll = (currentScrollbarValue == frame.scrollBarMaximum(QtCore.Qt.Vertical)) frame.setHtml(frame.toHtml() + html) view.show() if autoScroll: frame.scroll(0, frame.scrollBarMaximum(QtCore.Qt.Vertical)) else: frame.scroll(currentScrollbarValue.x(), currentScrollbarValue.y()) tabIndex = self._rooms[room.id]["tab"] tabBar = self._tabs.tabBar() isActiveTab = (self.isActiveWindow() and tabIndex == self._tabs.currentIndex()) if message.is_text() and not isActiveTab: self._rooms[room.id]["newMessages"] += 1 if self._rooms[room.id]["newMessages"] > 0: tabBar.setTabText(tabIndex, "{room} ({count})".format(room = room.name, count = self._rooms[room.id]["newMessages"])) if not isActiveTab and (alert or self._rooms[room.id]["newMessages"] > 0) and tabBar.tabTextColor(tabIndex) == self.COLORS["normal"]: tabBar.setTabTextColor(tabIndex, self.COLORS["alert" if alert else "new"]) notifyInactiveTab = self.getSetting("alerts", "notify_inactive_tab") if not isActiveTab and (alert or notifyInactiveTab): self._trayIcon.alert() if (alert and notify) or (not isActiveTab and notifyInactiveTab and message.is_text()): self._notify(room, "{} says: {}".format(message.user.name, message.body)) if updateRoom: if (message.is_joining() or message.is_leaving()): self.updateRoomUsers(room.id) elif message.is_upload(): self.updateRoomUploads(room.id) elif message.is_topic_change() and not message.is_by_current_user(): self._cfTopicChanged(room, message.body) if live and alertIsDirectPing and self.getSetting("program", "away") and self._idle: self._getWorker().speak(room, unicode("{user}: {message}").format( user = message.user.name, message = self.getSetting("program", "away_message") )) def _displayUpload(self, view, message): image = None if message.upload['content_type'].startswith("image/"): try: request = urllib2.Request(message.upload['url']) auth_header = base64.encodestring('{}:{}'.format(self._worker.getApiToken(), 'X')).replace('\n', '') request.add_header("Authorization", "Basic {}".format(auth_header)) image = urllib2.urlopen(request).read() except: pass if image: width = None try: imageFile = tempfile.NamedTemporaryFile('w+b') imageFile.write(image) (width, height) = Image.open(imageFile.name).size imageFile.close() maximumImageWidth = int(view.size().width() * 0.7) # 70% of viewport if width > maximumImageWidth: width = maximumImageWidth except: pass html = self.MESSAGES["image"].format( type = message.upload['content_type'], data = base64.encodestring(image), url = message.upload['url'], name = message.upload['name'], attribs = "width=\"{width}\" ".format(width=width) if width else "" ) else: html = self.MESSAGES["upload"].format( url = message.upload['url'], name = message.upload['name'] ) return html def _matchesAlert(self, message): matches = False regexes = [] words = self.getSetting("alerts", "matches").split(";") for word in words: regexes.append("\\b{word}\\b".format(word=word)) for regex in regexes: if QtCore.QString(message).contains(QtCore.QRegExp(regex, QtCore.Qt.CaseInsensitive)): matches = True break return matches def _cfConnected(self, user, rooms): self._connecting = False self._connected = True self._rooms = {} self._toolBar["rooms"].clear() for room in rooms: self._toolBar["rooms"].addItem(room["name"], room) self.statusBar().showMessage(self._("{user} connected to Campfire").format(user=user.name), 5000) self._updateLayout() if not self._pingTimer: self._pingTimer = QtCore.QTimer(self) self.connect(self._pingTimer, QtCore.SIGNAL("timeout()"), self.ping) self._pingTimer.start(60000) # Ping every minute if self.getSetting("program", "away"): self._setUpIdleTracker() if self.getSetting("connection", "join"): rooms = self.getSetting("connection", "rooms") if rooms: for roomId in rooms.split(","): count = self._toolBar["rooms"].count() if count: roomIndex = None for i in range(count): data = self._toolBar["rooms"].itemData(i) if not data.isNull(): data = data.toMap() for key in data: if str(key) == "id" and str(data[key].toString()) == roomId: roomIndex = i break; if roomIndex is not None: break if roomIndex is not None: self.joinRoom(roomIndex) def _cfDisconnected(self): if self._pingTimer: self._pingTimer.stop() self._pingTimer = None if self._idleTimer: self._setUpIdleTracker(False) self._connecting = False self._connected = False self._rooms = {} self._worker = None self.statusBar().clearMessage() def _cfRoomJoined(self, room, messages=[], rejoined=False): if room.id not in self._rooms: return if not rejoined: self._rooms[room.id].update(self._setupRoomUI(room)) self._rooms[room.id]["room"] = room self._rooms[room.id]["stream"] = self._worker.getStream(room) self.updateRoomUsers(room.id) self.updateRoomUploads(room.id) if not rejoined: self.statusBar().showMessage(self._("Joined room {room}").format(room=room.name), 5000) self._updatedRoomsList() if not rejoined and messages: for message in messages: self._cfStreamMessage(room, message, live=False, updateRoom=False) def _cfSpoke(self, room, message): self._cfStreamMessage(room, message, live=False) self.statusBar().clearMessage() def _cfRoomLeft(self, room): if self._rooms[room.id]["stream"]: self._rooms[room.id]["stream"].stop().join() if self._rooms[room.id]["upload"]: self._rooms[room.id]["upload"].stop().join() self._tabs.removeTab(self._rooms[room.id]["tab"]) del self._rooms[room.id] self.statusBar().showMessage(self._("Left room {room}").format(room=room.name), 5000) self._updatedRoomsList() def _cfRoomUsers(self, room, users, pinging=False): # We may be disconnecting while still processing the list if not room.id in self._rooms: return if not pinging: self.statusBar().clearMessage() self._rooms[room.id]["usersList"].clear() for user in users: item = QtGui.QListWidgetItem(user["name"]) item.setData(QtCore.Qt.UserRole, user) self._rooms[room.id]["usersList"].addItem(item) def _cfRoomUploads(self, room, uploads): # We may be disconnecting while still processing the list if not room.id in self._rooms: return self.statusBar().clearMessage() label = self._rooms[room.id]["filesLabel"] if uploads: html = "" for upload in uploads: html += "{br}• <a href=\"{url}\">{name}</a>".format( br = "<br />" if html else "", url = upload["full_url"], name = upload["name"] ) html = "{text}<br />{html}".format( text = self._("Latest uploads:"), html = html ) label.setText(html) if not label.isVisible(): label.show() elif label.isVisible(): label.setText("") label.hide() def _cfUploadProgress(self, room, current, total): if not room.id in self._rooms: return progressBar = self._rooms[room.id]["uploadProgressBar"] if not self._rooms[room.id]["uploadWidget"].isVisible(): self._rooms[room.id]["uploadWidget"].show() progressBar.setMaximum(total) progressBar.setValue(current) def _cfUploadFinished(self, room): if not room.id in self._rooms: return self._rooms[room.id]["upload"].join() self._rooms[room.id]["upload"] = None self._rooms[room.id]["uploadWidget"].hide() def _cfTopicChanged(self, room, topic): if not room.id in self._rooms: return self._rooms[room.id]["topicLabel"].setText(topic) self.statusBar().clearMessage() def _cfConnectError(self, error): self._cfDisconnected() self._updateLayout() self._cfError(error) def _cfError(self, error): self.statusBar().clearMessage() if not self._connected: QtGui.QMessageBox.critical(self, "Error", self._("Error while connecting: {error}".format(error = str(error)))) else: QtGui.QMessageBox.critical(self, "Error", str(error)) def _cfRoomError(self, error, room): self.statusBar().clearMessage() if isinstance(error, RuntimeError): (code, message) = error if code == 401: self.statusBar().showMessage(self._("Disconnected from room. Rejoining room {room}...").format(room=room.name), 5000) self._rooms[room.id]["stream"].stop().join() self._getWorker().join(room.id, True) return QtGui.QMessageBox.critical(self, "Error", str(error)) def _roomSelected(self, index): self._updatedRoomsList(index) def _upload(self, room, path): self._rooms[room.id]["upload"] = self._worker.upload(room, path) self._updateRoomLayout() def _roomTabClose(self, tabIndex): for roomId in self._rooms: if self._rooms[roomId]["tab"] == tabIndex: self.leaveRoom(roomId) break def _roomTabFocused(self): tabIndex = self._tabs.currentIndex() if tabIndex < 0 or not self.isActiveWindow(): return room = self._roomInTabIndex(tabIndex) if not room: return tabBar = self._tabs.tabBar() if self._rooms[room.id]["newMessages"] > 0: self._rooms[room.id]["newMessages"] = 0 tabBar.setTabText(tabIndex, room.name) if tabBar.tabTextColor(tabIndex) != self.COLORS["normal"]: tabBar.setTabTextColor(tabIndex, self.COLORS["normal"]) self._updateRoomLayout() def _roomInTabIndex(self, index): room = None for key in self._rooms: if self._rooms[key]["tab"] == index: room = self._rooms[key]["room"] break return room def _roomInIndex(self, index): room = {} data = self._toolBar["rooms"].itemData(index) if not data.isNull(): data = data.toMap() for key in data: room[str(key)] = unicode(data[key].toString()) return room def _connectWorkerSignals(self, worker): self.connect(worker, QtCore.SIGNAL("error(PyQt_PyObject)"), self._cfError) self.connect(worker, QtCore.SIGNAL("connected(PyQt_PyObject, PyQt_PyObject)"), self._cfConnected) self.connect(worker, QtCore.SIGNAL("connectError(PyQt_PyObject)"), self._cfConnectError) self.connect(worker, QtCore.SIGNAL("streamError(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomError) self.connect(worker, QtCore.SIGNAL("uploadError(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomError) self.connect(worker, QtCore.SIGNAL("joined(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfRoomJoined) self.connect(worker, QtCore.SIGNAL("spoke(PyQt_PyObject, PyQt_PyObject)"), self._cfSpoke) self.connect(worker, QtCore.SIGNAL("streamMessage(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfStreamMessage) self.connect(worker, QtCore.SIGNAL("left(PyQt_PyObject)"), self._cfRoomLeft) self.connect(worker, QtCore.SIGNAL("users(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUsers) self.connect(worker, QtCore.SIGNAL("uploads(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUploads) self.connect(worker, QtCore.SIGNAL("uploadProgress(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfUploadProgress) self.connect(worker, QtCore.SIGNAL("uploadFinished(PyQt_PyObject)"), self._cfUploadFinished) self.connect(worker, QtCore.SIGNAL("topicChanged(PyQt_PyObject, PyQt_PyObject)"), self._cfTopicChanged) def _getWorker(self): if not hasattr(self, "_workers"): self._workers = [] if self._workers: for worker in self._workers: if worker.isFinished(): return worker worker = copy.copy(self._worker) self._connectWorkerSignals(worker) self._workers.append(worker) return worker def _updatedRoomsList(self, index=None): if not index: index = self._toolBar["rooms"].currentIndex() room = self._roomInIndex(index) self._toolBar["join"].setEnabled(False) if not room or room["id"] not in self._rooms: self._toolBar["join"].setEnabled(True) centralWidget = self.centralWidget() if not self._tabs.count(): centralWidget.hide() else: centralWidget.show() def _notify(self, room, message): raise NotImplementedError("_notify() must be implemented") def _updateRoomLayout(self): room = self.getCurrentRoom() if room: canUpload = not self._rooms[room.id]["upload"] uploadButton = self._rooms[room.id]["uploadButton"] if ( (canUpload and not uploadButton.isEnabled()) or (not canUpload and uploadButton.isEnabled()) ): uploadButton.setEnabled(canUpload) def _updateLayout(self): self._menus["file"]["connect"].setEnabled(not self._connected and self._canConnect and not self._connecting) self._menus["file"]["disconnect"].setEnabled(self._connected) roomsEmpty = self._toolBar["rooms"].count() == 1 and self._toolBar["rooms"].itemData(0).isNull() if not roomsEmpty and (not self._connected or not self._toolBar["rooms"].count()): self._toolBar["rooms"].clear() self._toolBar["rooms"].addItem(self._("No rooms available")) self._toolBar["rooms"].setEnabled(False) elif not roomsEmpty: self._toolBar["rooms"].setEnabled(True) self._toolBar["roomsLabel"].setEnabled(self._toolBar["rooms"].isEnabled()) self._toolBar["join"].setEnabled(self._toolBar["rooms"].isEnabled()) def _setupRoomUI(self, room): topic = room.topic if room.topic else "" topicLabel = ClickableQLabel(topic) topicLabel.setToolTip(self._("Click here to change room's topic")) topicLabel.setWordWrap(True) self.connect(topicLabel, QtCore.SIGNAL("clicked()"), self.changeTopic) view = SnakeFireWebView(self) view.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) frame = view.page().mainFrame() #Send all link clicks to systems web browser view.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks) def linkClicked(url): webbrowser.open(str(url.toString())) view.connect(view, QtCore.SIGNAL("linkClicked (const QUrl&)"), linkClicked) # Support auto scroll when needed def autoScroll(size): frame.scroll(0, size.height()) frame.connect(frame, QtCore.SIGNAL("contentsSizeChanged (const QSize&)"), autoScroll) usersList = QtGui.QListWidget() filesLabel = QtGui.QLabel("") filesLabel.setOpenExternalLinks(True) filesLabel.setWordWrap(True) filesLabel.hide() uploadButton = QtGui.QPushButton(self._("&Upload new file")) self.connect(uploadButton, QtCore.SIGNAL("clicked()"), self.uploadFile) uploadProgressBar = QtGui.QProgressBar() uploadProgressLabel = QtGui.QLabel(self._("Uploading:")) uploadCancelButton = QtGui.QPushButton(self._("Cancel")) self.connect(uploadCancelButton, QtCore.SIGNAL("clicked()"), self.uploadCancel) uploadLayout = QtGui.QHBoxLayout() uploadLayout.addWidget(uploadProgressLabel) uploadLayout.addWidget(uploadProgressBar) uploadLayout.addWidget(uploadCancelButton) uploadWidget = QtGui.QWidget() uploadWidget.setLayout(uploadLayout) uploadWidget.hide() leftFrameLayout = QtGui.QVBoxLayout() leftFrameLayout.addWidget(topicLabel) leftFrameLayout.addWidget(view) leftFrameLayout.addWidget(uploadWidget) rightFrameLayout = QtGui.QVBoxLayout() rightFrameLayout.addWidget(QtGui.QLabel(self._("Users in room:"))) rightFrameLayout.addWidget(usersList) rightFrameLayout.addWidget(filesLabel) rightFrameLayout.addWidget(uploadButton) rightFrameLayout.addStretch(1) leftFrame = QtGui.QWidget() leftFrame.setLayout(leftFrameLayout) rightFrame = QtGui.QWidget() rightFrame.setLayout(rightFrameLayout) splitter = QtGui.QSplitter() splitter.addWidget(leftFrame) splitter.addWidget(rightFrame) splitter.setSizes([splitter.size().width() * 0.75, splitter.size().width() * 0.25]) index = self._tabs.addTab(splitter, room.name) self._tabs.setCurrentIndex(index) if not self.COLORS["normal"]: self.COLORS["normal"] = self._tabs.tabBar().tabTextColor(index) else: self._tabs.tabBar().setTabTextColor(index, self.COLORS["normal"]) return { "tab": index, "view": view, "frame": frame, "usersList": usersList, "topicLabel": topicLabel, "filesLabel": filesLabel, "uploadButton": uploadButton, "uploadWidget": uploadWidget, "uploadProgressBar": uploadProgressBar, "uploadProgressLabel": uploadProgressLabel } def _setupUI(self): self.setWindowTitle(self.NAME) self._addMenu() self._addToolbar() self._tabs = QtGui.QTabWidget() self._tabs.setTabsClosable(True) self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused) self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose) self._editor = QtGui.QPlainTextEdit() self._editor.setFixedHeight(self._editor.fontMetrics().height() * 2) self._editor.installEventFilter(SuggesterKeyPressEventFilter(self, Suggester(self._editor))) speakButton = QtGui.QPushButton(self._("&Send")) self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak) grid = QtGui.QGridLayout() grid.setRowStretch(0, 1) grid.addWidget(self._tabs, 0, 0, 1, -1) grid.addWidget(self._editor, 2, 0) grid.addWidget(speakButton, 2, 1) widget = QtGui.QWidget() widget.setLayout(grid) self.setCentralWidget(widget) tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self) self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused) widget.installEventFilter(tabWidgetFocusEventFilter) self.centralWidget().hide() size = self.getSetting("window", "size") if not size: size = QtCore.QSize(640, 480) self.resize(size) position = self.getSetting("window", "position") if not position: screen = QtGui.QDesktopWidget().screenGeometry() position = QtCore.QPoint((screen.width()-size.width())/2, (screen.height()-size.height())/2) self.move(position) self._updateLayout() menu = QtGui.QMenu(self) menu.addAction(self._menus["file"]["connect"]) menu.addAction(self._menus["file"]["disconnect"]) menu.addSeparator() menu.addAction(self._menus["file"]["exit"]) self._trayIcon = Systray(self._icon, self) self._trayIcon.setContextMenu(menu) self._trayIcon.setToolTip(self.DESCRIPTION) def _addMenu(self): self._menus = { "file": { "connect": self._createAction(self._("&Connect"), self.connectNow, icon="connect.png"), "disconnect": self._createAction(self._("&Disconnect"), self.disconnectNow, icon="disconnect.png"), "exit": self._createAction(self._("E&xit"), self.exit) }, "settings": { "alerts": self._createAction(self._("&Alerts..."), self.alerts, icon="alerts.png"), "options": self._createAction(self._("&Options..."), self.options) }, "help": { "about": self._createAction(self._("A&bout")) } } menu = self.menuBar() file_menu = menu.addMenu(self._("&File")) file_menu.addAction(self._menus["file"]["connect"]) file_menu.addAction(self._menus["file"]["disconnect"]) file_menu.addSeparator() file_menu.addAction(self._menus["file"]["exit"]) settings_menu = menu.addMenu(self._("S&ettings")) settings_menu.addAction(self._menus["settings"]["alerts"]) settings_menu.addSeparator() settings_menu.addAction(self._menus["settings"]["options"]) help_menu = menu.addMenu(self._("&Help")) help_menu.addAction(self._menus["help"]["about"]) def _addToolbar(self): self._toolBar = { "connect": self._menus["file"]["connect"], "disconnect": self._menus["file"]["disconnect"], "roomsLabel": QtGui.QLabel(self._("Rooms:")), "rooms": QtGui.QComboBox(), "join": self._createAction(self._("Join room"), self.joinRoom, icon="join.png"), "alerts": self._menus["settings"]["alerts"] } self.connect(self._toolBar["rooms"], QtCore.SIGNAL("currentIndexChanged(int)"), self._roomSelected) toolBar = self.toolBar() toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) toolBar.addAction(self._toolBar["connect"]) toolBar.addAction(self._toolBar["disconnect"]) toolBar.addSeparator(); toolBar.addWidget(self._toolBar["roomsLabel"]) toolBar.addWidget(self._toolBar["rooms"]) toolBar.addAction(self._toolBar["join"]) toolBar.addSeparator(); toolBar.addAction(self._toolBar["alerts"]) def _createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): """ Create an action """ action = QtGui.QAction(text, self) if icon is not None: if not isinstance(icon, QtGui.QIcon): action.setIcon(QtGui.QIcon(":/icons/{icon}".format(icon=icon))) else: action.setIcon(icon) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, QtCore.SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def _plainTextToHTML(self, string): return string.replace("<", "<").replace(">", ">").replace("\n", "<br />") def _autoLink(self, string): urlre = re.compile("(\(?https?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|])(\">|</a>)?") urls = urlre.findall(string) cleanUrls = [] for url in urls: if url[1]: continue currentUrl = url[0] if currentUrl[0] == '(' and currentUrl[-1] == ')': currentUrl = currentUrl[1:-1] if currentUrl in cleanUrls: continue cleanUrls.append(currentUrl) string = re.sub("(?<!(=\"|\">))" + re.escape(currentUrl), "<a href=\"" + currentUrl + "\">" + currentUrl + "</a>", string) return string def _(self, string, module=None): return str(QtCore.QCoreApplication.translate(module or Snakefire.NAME, string))
class ScudCloud(QtGui.QMainWindow): plugins = True debug = False forceClose = False messages = 0 def __init__(self, parent=None, settings_path=""): super(ScudCloud, self).__init__(parent) self.setWindowTitle('ScudCloud') self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id( "scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) self.stackedWidget = QtGui.QStackedWidget() self.stackedWidget.addWidget(webView) centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.addMenu() self.tray = Systray(self) self.systray(ScudCloud.minimized) self.installEventFilter(self) if self.identifier is None: webView.load(QtCore.QUrl(Resources.SIGNIN_URL)) else: webView.load(QtCore.QUrl(self.domain())) webView.show() # Starting unread msgs counter self.setupTimer() def setupTimer(self): timer = QTimer(self) timer.timeout.connect(self.count) timer.setInterval(2000) timer.start() def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # Required by Youtube videos (HTML5 video support only on Qt5) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, self.plugins) # We don't want Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # We don't need History QWebSettings.globalSettings().setAttribute( QWebSettings.PrivateBrowsingEnabled, True) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute( QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute( QWebSettings.DeveloperExtrasEnabled, self.debug) def toggleFullScreen(self): if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def restore(self): geometry = self.settings.value("geometry") if geometry is not None: self.restoreGeometry(geometry) windowState = self.settings.value("windowState") if windowState is not None: self.restoreState(windowState) else: self.showMaximized() def systray(self, show=None): if show is None: show = self.settings.value("Systray") == "True" if show: self.tray.show() self.menus["file"]["close"].setEnabled(True) self.settings.setValue("Systray", "True") else: self.tray.setVisible(False) self.menus["file"]["close"].setEnabled(False) self.settings.setValue("Systray", "False") def readZoom(self): default = 1 if self.settings.value("Zoom") is not None: default = float(self.settings.value("Zoom")) return default def setZoom(self, factor=1): if factor > 0: for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) widget.setZoomFactor(factor) self.settings.setValue("Zoom", factor) def zoomIn(self): self.setZoom(self.current().zoomFactor() + 0.1) def zoomOut(self): self.setZoom(self.current().zoomFactor() - 0.1) def zoomReset(self): self.setZoom() def addMenu(self): self.menus = { "file": { "preferences": self.createAction("Preferences", lambda: self.current().preferences()), "systray": self.createAction("Close to Tray", self.systray, None, True), "addTeam": self.createAction("Sign in to Another Team", lambda: self.current().addTeam()), "signout": self.createAction("Signout", lambda: self.current().logout()), "close": self.createAction("Close", self.close, QKeySequence.Close), "exit": self.createAction("Quit", self.exit, QKeySequence.Quit) }, "edit": { "undo": self.current().pageAction(QtWebKit.QWebPage.Undo), "redo": self.current().pageAction(QtWebKit.QWebPage.Redo), "cut": self.current().pageAction(QtWebKit.QWebPage.Cut), "copy": self.current().pageAction(QtWebKit.QWebPage.Copy), "paste": self.current().pageAction(QtWebKit.QWebPage.Paste), "back": self.current().pageAction(QtWebKit.QWebPage.Back), "forward": self.current().pageAction(QtWebKit.QWebPage.Forward), "reload": self.current().pageAction(QtWebKit.QWebPage.Reload) }, "view": { "zoomin": self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn), "zoomout": self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut), "reset": self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0), "fullscreen": self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11) }, "help": { "help": self.createAction("Help and Feedback", self.current().help, QKeySequence.HelpContents), "center": self.createAction("Slack Help Center", self.current().helpCenter), "about": self.createAction("About", lambda: self.current().about()) } } menu = self.menuBar() fileMenu = menu.addMenu("&File") fileMenu.addAction(self.menus["file"]["preferences"]) fileMenu.addAction(self.menus["file"]["systray"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["addTeam"]) fileMenu.addAction(self.menus["file"]["signout"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["close"]) fileMenu.addAction(self.menus["file"]["exit"]) editMenu = menu.addMenu("&Edit") editMenu.addAction(self.menus["edit"]["undo"]) editMenu.addAction(self.menus["edit"]["redo"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["cut"]) editMenu.addAction(self.menus["edit"]["copy"]) editMenu.addAction(self.menus["edit"]["paste"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["back"]) editMenu.addAction(self.menus["edit"]["forward"]) editMenu.addAction(self.menus["edit"]["reload"]) viewMenu = menu.addMenu("&View") viewMenu.addAction(self.menus["view"]["zoomin"]) viewMenu.addAction(self.menus["view"]["zoomout"]) viewMenu.addAction(self.menus["view"]["reset"]) viewMenu.addSeparator() viewMenu.addAction(self.menus["view"]["fullscreen"]) helpMenu = menu.addMenu("&Help") helpMenu.addAction(self.menus["help"]["help"]) helpMenu.addAction(self.menus["help"]["center"]) helpMenu.addSeparator() helpMenu.addAction(self.menus["help"]["about"]) self.enableMenus(False) showSystray = self.settings.value("Systray") == "True" self.menus["file"]["systray"].setChecked(showSystray) self.menus["file"]["close"].setEnabled(showSystray) def enableMenus(self, enabled): self.menus["file"]["preferences"].setEnabled(enabled == True) self.menus["file"]["addTeam"].setEnabled(enabled == True) self.menus["file"]["signout"].setEnabled(enabled == True) self.menus["help"]["help"].setEnabled(enabled == True) def createAction(self, text, slot, shortcut=None, checkable=False): action = QtGui.QAction(text, self) if shortcut is not None: action.setShortcut(shortcut) action.triggered.connect(slot) if checkable: action.setCheckable(True) return action def domain(self): if self.identifier.endswith(".slack.com"): return self.identifier else: return "https://" + self.identifier + ".slack.com" def current(self): return self.stackedWidget.currentWidget() def teams(self, teams): if teams is not None and len(teams) > 1: self.leftPane.show() for t in teams: try: self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], t['team_icon']['image_88'], t == teams[0]) except: self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], '', t == teams[0]) def switchTo(self, url): qUrl = QtCore.QUrl(url) index = -1 for i in range(0, self.stackedWidget.count()): if self.stackedWidget.widget(i).url().toString().startswith(url): index = i break if index != -1: self.stackedWidget.setCurrentIndex(index) else: webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) webView.load(qUrl) webView.show() self.stackedWidget.addWidget(webView) self.stackedWidget.setCurrentWidget(webView) self.quicklist(self.current().listChannels()) self.enableMenus(self.current().isConnected()) # Save the last used team as default self.settings.setValue("Domain", 'https://' + qUrl.host()) def eventFilter(self, obj, event): if event.type( ) == QtCore.QEvent.ActivationChange and self.isActiveWindow(): self.focusInEvent(event) if event.type() == QtCore.QEvent.KeyPress: # Ctrl + <n> if QtGui.QApplication.keyboardModifiers( ) == QtCore.Qt.ControlModifier: if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0) elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1) elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2) elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3) elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4) elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5) elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6) elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7) elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8) # ctrl + tab elif event.key() == QtCore.Qt.Key_Tab: self.leftPane.clickNext(1) # ctrl + backtab if (QtGui.QApplication.keyboardModifiers() & QtCore.Qt.ControlModifier) and ( QtGui.QApplication.keyboardModifiers() & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_Backtab: self.leftPane.clickNext(-1) # Ctrl + Shift + <key> if (QtGui.QApplication.keyboardModifiers() & QtCore.Qt.ShiftModifier) and ( QtGui.QApplication.keyboardModifiers() & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_V: self.current().createSnippet() return QtGui.QMainWindow.eventFilter(self, obj, event) def focusInEvent(self, event): self.launcher.set_property("urgent", False) self.tray.stopAlert() def titleChanged(self): self.setWindowTitle(self.current().title()) def closeEvent(self, event): if not self.forceClose and self.settings.value("Systray") == "True": self.hide() event.ignore() else: self.cookiesjar.save() self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) def show(self): self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.activateWindow() self.setVisible(True) def exit(self): self.forceClose = True self.close() def quicklist(self, channels): if Dbusmenu is not None: ql = Dbusmenu.Menuitem.new() self.launcher.set_property("quicklist", ql) if channels is not None: for c in channels: if c['is_member']: item = Dbusmenu.Menuitem.new() item.property_set(Dbusmenu.MENUITEM_PROP_LABEL, "#" + c['name']) item.property_set("id", c['name']) item.property_set_bool(Dbusmenu.MENUITEM_PROP_VISIBLE, True) item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel) ql.child_append(item) self.launcher.set_property("quicklist", ql) def notify(self, title, message): self.notifier.notify(title, message) self.alert() def alert(self): if not self.isActiveWindow(): self.launcher.set_property("urgent", True) self.tray.alert() def count(self): total = 0 for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) widget.count() if widget.messages == 0: self.leftPane.stopAlert(widget.team()) else: self.leftPane.alert(widget.team()) if widget.messages is not None: total += widget.messages if total > self.messages: self.alert() if 0 == total: self.launcher.set_property("count_visible", False) self.tray.setCounter(0) else: self.tray.setCounter(total) self.launcher.set_property("count", total) self.launcher.set_property("count_visible", True) self.messages = total