def main(): global app # sys.argv.extend(['-platform', 'eglfs']) # Qt Charts uses Qt Graphics View Framework for drawing, therefore QApplication must be used. app = QApplication(sys.argv) viewer = QQuickView() # The following are needed to make examples run without having to install the module # in desktop environments. extraImportPath = QGuiApplication.applicationDirPath() if sys.platform == 'win32': extraImportPath += "/../../../../qml" else: extraImportPath += "/../../../qml" viewer.engine().addImportPath(extraImportPath) viewer.engine().quit.connect(app.quit) viewer.setTitle("QML Oscilloscope") dataSource = datasource.DataSource(viewer) viewer.rootContext().setContextProperty("dataSource", dataSource) main_qml = path.dirname(__file__) + "/qml/qmloscilloscope/main.qml" viewer.setSource(QUrl(main_qml)) viewer.setResizeMode(QQuickView.SizeRootObjectToView) viewer.setColor(QColor("#404040")) viewer.show() return app.exec_()
class View(object): def __init__(self, iface, chart): dir_path = os.path.dirname(os.path.realpath(__file__)) qml = os.path.join(dir_path, "qml", "scatterplot.qml") self.view = QQuickView() self.view.setResizeMode(QQuickView.SizeRootObjectToView) self.view.rootContext().setContextProperty("pychart", chart) self.view.setColor(QColor("#000000")) self.view.setSource(QUrl.fromLocalFile(qml)) self.container = QWidget.createWindowContainer(self.view) self.widget = QDockWidget() self.widget.setWidget(self.container) iface.addDockWidget(Qt.BottomDockWidgetArea, self.widget) self.read_settings() def read_settings(self, settings=None): if not settings: settings = Settings.Snapshot() self.view.setColor(QColor(settings.background_color)) def show(self): self.widget.show() def hide(self): self.widget.hide()
def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setWindowTitle('Read OpenVSLAM map') self.mapInfo = MapInfo() qml_view = QQuickView() qml_view.rootContext().setContextProperty('mapInfo', self.mapInfo) qml_view.setSource( QUrl( os.path.join(os.path.dirname(os.path.realpath(__file__)), 'read_map.qml'))) qml_view.setResizeMode(QQuickView.SizeRootObjectToView) self.qml_gui = qml_view.rootObject() qml_view_container = QWidget.createWindowContainer(qml_view, self) qml_view_container.setMinimumSize(800, 200) self.setCentralWidget(qml_view_container) self.mapInfo.filenameChanged.connect(self.read_data) self.mapInfo.visualiseCloud.connect(self.pptk) self.mapInfo.visualiseTrajectory.connect(self.pyplot) self.mapInfo.exportCloud.connect(self.export_cloud) self.data = None self.cloud = None self.trajectory = None
class CountdownApp(QObject): QMLFILE = 'main.qml' def __init__(self): super(QObject, self).__init__() self.app = QGuiApplication(sys.argv) self.view = QQuickView() self.view.setResizeMode(QQuickView.SizeRootObjectToView) #self.view.engine().quit.connect(self.app.quit) self.cds = CountdownList() self.cdt = [] for name, targetDatetime in { "silvester": datetime(2018, 1, 1, 0, 0, 0), "geburtstag": datetime(2018, 3, 12, 0, 0, 0) }.items(): cdobj = CountdownData() countdown = CountdownTimer(cdobj, targetDatetime, name) countdown.start() self.cds.append(cdobj) self.cdt.append(countdown) self.view.rootContext().setContextProperty('countdowns', self.cds) self.view.setSource(QUrl(self.QMLFILE)) self.t = QTimer() self.t.timeout.connect(self.addCountdown) self.t.start(10000) def run(self): self.view.show() sys.exit(self.app.exec_()) @pyqtSlot() def addCountdown(self): for name, targetDatetime in { "antrittsvorlesung": datetime(2018, 1, 19, 0, 0, 0) }.items(): cdobj = CountdownData() countdown = CountdownTimer(cdobj, targetDatetime, name) countdown.start() self.cds.append(cdobj) self.cdt.append(countdown)
def main(arguments): app = QGuiApplication(sys.argv) view = QQuickView() f = QFile(':/default.txt') f.open(QIODevice.ReadOnly) model = TreeModel(f.readAll()) f.close() rootContext = view.rootContext().setContextProperty('model', model) view.setSource(QUrl.fromLocalFile('TreeModel.qml')) view.show() sys.exit(app.exec_())
def run(): signal.signal(signal.SIGINT, signal.SIG_DFL) app = QGuiApplication(sys.argv) view = QQuickView() view.setTitle('Hot reloading demo') qml_engine = view.rootContext().engine() qml_engine.addImportPath(lib_dir_path) notifier = HotReloadNotifier(demo_dir_path, qml_engine, parent=app) view.rootContext().setContextProperty('hotReloadNotifier', notifier) qml_url = QUrl.fromLocalFile(os.path.join(demo_dir_path, 'Demo.qml')) view.setSource(qml_url) view.show() exit_code = app.exec_() # notifier.stop() # seems like this is not needed sys.exit(exit_code)
class VVSQMLApp(QObject): QMLFILE = 'gui.qml' def __init__(self, connections): super(QObject, self).__init__() self.app = QGuiApplication(sys.argv) self.view = QQuickView() self.view.setResizeMode(QQuickView.SizeRootObjectToView) if settings['alwaysOnTop']: self.view.setFlags(Qt.WindowStaysOnTopHint) self.con = [] for connection in connections: updaterThread = VVSConnectionUpdater( connection[0], connection[1], connection[2], updateDelay=settings['updateDelay']) updaterThread.start() self.con.append(updaterThread) #print(connection) #self.con = VVSConnectionUpdater('5006021', 'X60', 'Leonberg Bf') #self.con.start() #print(self.con) self.view.rootContext().setContextProperty('con', self.con) self.view.setSource(QUrl(self.QMLFILE)) #Setup notifications VVSNotifier.setup(self.con) def run(self): if settings['fullscreen']: self.view.showFullScreen() else: self.view.show() sys.exit(self.app.exec_())
def main(): """Main Function Entry.""" app = QApplication(sys.argv) # Create a label and set its properties applable = QQuickView() applable.setSource(QUrl('basic.qml')) conn = SlotClass() context = applable.rootContext() context.setContextProperty("conn", conn) # Show the Label applable.show() # Execute the Application and Exit app.exec_() sys.exit()
def main(): global VIEW global APP APP = QGuiApplication(sys.argv) VIEW = QQuickView() url = QUrl('main.qml') VIEW.setSource(url) submit = submitUserInput() context = VIEW.rootContext() context.setContextProperty("submit", submit) VIEW.show() sys.exit(APP.exec_())
class MainWindow(QtCore.QObject): def __init__(self): QtCore.QObject.__init__(self) self._controller = Controller() self.view = QQuickView() full_path = os.path.realpath(__file__) folder = os.path.dirname(full_path) qml_file = os.path.join(folder, 'qml', 'App.qml') qml_qurl = QtCore.QUrl.fromLocalFile(qml_file) self.view.setSource(qml_qurl) # Add context properties to use this objects from qml rc = self.view.rootContext() rc.setContextProperty('controller', self._controller) def show(self): self.view.show()
def initPlugins(self): # init QML plugin container if hasattr(self, 'stackedWidget'): del (self.stackedWidget) self.stackedWidget = QStackedWidget(self) self.plugins = [] for pluginName in self.config.PLUGINS: view = QQuickView() view.setFlags(Qt.SubWindow) # add clientapi into the plugin context context = view.rootContext() context.setContextProperty('zulApi', self.zulApi) context.setContextProperty('GUI', self) # load plugin translations if i18n subfolder exists i18nDir = 'plugins/{}/i18n'.format(pluginName) if os.path.exists(i18nDir): translator = QtCore.QTranslator() fileName = 'send_'.format(QtCore.QLocale.system().name()) #fileName = 'send_fr' translator.load(fileName, i18nDir) self.app.installTranslator(translator) # load QML file plugin_index_path = 'plugins/{}/index.qml'.format(pluginName) view.setSource(QUrl(plugin_index_path)) plugin = view.rootObject() self.plugins.append(plugin) # call plugin init callback plugin.init() # add the plugin in the container container = QWidget.createWindowContainer(view, self) self.stackedWidget.addWidget(container)
def main(): app = QGuiApplication(sys.argv) app.setApplicationName('InfiniteCopy') openDataBase() view = QQuickView() clipboardItemModel = ClipboardItemModel() clipboardItemModel.create() filterProxyModel = QSortFilterProxyModel() filterProxyModel.setSourceModel(clipboardItemModel) clipboard = Clipboard() clipboard.setFormats([ mimeText, mimeHtml, mimePng, mimeSvg ]) clipboard.changed.connect(clipboardItemModel.addItem) engine = view.engine() imageProvider = ClipboardItemModelImageProvider(clipboardItemModel) engine.addImageProvider("items", imageProvider) context = view.rootContext() context.setContextProperty('clipboardItemModel', clipboardItemModel) context.setContextProperty('clipboardItemModelFilterProxy', filterProxyModel) context.setContextProperty('clipboard', clipboard) view.setSource(QUrl.fromLocalFile('qml/MainWindow.qml')) view.setGeometry(100, 100, 400, 240) view.show() engine.quit.connect(QGuiApplication.quit) return app.exec_()
def createWindow(): view = QQuickView() view.setSurfaceType(QSurface.OpenGLSurface) fmt = QSurfaceFormat() fmt.setAlphaBufferSize(8) fmt.setRenderableType(QSurfaceFormat.OpenGL) view.setFormat(fmt) color = QColor() color.setRedF(0.0) color.setGreenF(0.0) color.setBlueF(0.0) color.setAlphaF(0.0) view.setColor(color) view.setClearBeforeRendering(True) view.setFlags(Qt.FramelessWindowHint | Qt.ToolTip | Qt.WindowStaysOnTopHint) context = view.rootContext() return (view, context)
def setup_from_vispy_pyqt5(self, root_window: QMainWindow, vispy_canvas: MyCanvas, quick_view: QQuickView): self.root_win = root_window self.vispy_canvas = vispy_canvas self.top_panel = quick_view.rootContext().setContextProperty( "visualizer_o", self)
appLabel.setSource(QUrl('main.qml')) #appLabel.load(QUrl('main2.qml')) # Show the Label appLabel.show() # Create a QML engine. engine = QQmlEngine() # Initialize PhotoBoothEngine. pbengine = PhotoBoothEngine() pbengine.on_status.connect(appLabel.rootObject().status) pbengine.on_update_filter_preview.connect( appLabel.rootObject().updateImageFilterPreview) appLabel.rootContext().setContextProperty('pbengine', pbengine) # Create a component factory and load the QML script. print("Hello") component = QQmlComponent(appLabel.engine()) component.loadUrl(QUrl('TextStatusFly.qml')) print("Hello2") asdf = component.create(appLabel.rootContext()) print("Hello3") asdf.setParentItem(appLabel.rootObject()) asdf.setParent(appLabel.rootObject()) print("Hello4") #asdf.setProperty("targetX", 100)
class Example(QWidget): @pyqtSlot(QVariant) def sendImgToGallery(self, aword): print(aword) #o = self.view.findChild(QObject, 'textEdit') #print(o) print("kek") def getTagsString(self, tagsArray): tagsString = "" for tag in tagsArray: tagsString += tag+", " return tagsString def buttonClicked(self): file = str(QFileDialog.getExistingDirectory(self, "Select Directory")) if (file): QMetaObject.invokeMethod( self.view.rootObject(), "clear") #QMetaObject.invokeMethod( # self.view.rootObject(), "showBusyIndicator") self.startProcessing(file) def selectSortDirPath(self): file = str(QFileDialog.getExistingDirectory(self, "Select Directory")) if (file): self.sortDirPath = file QMetaObject.invokeMethod(self.view.rootObject(), "setSortDitPathTextField", Q_ARG(QVariant, file)) def getAllImagePathFromDir(self, directoryPath): fileNameArray = [] for (dirpath, dirnames, filenames) in walk(directoryPath): fileNameArray.extend(filenames) break for idx in range(len(fileNameArray)): fileNameArray[idx] = directoryPath+"/"+fileNameArray[idx] return fileNameArray def getImageInformationFromKhiena(self, fileNameArray): imgInfoArray = [] print(fileNameArray) for idx in range(len(fileNameArray)): imgInfoArray.append(apiKhiena.imgSearch(fileNameArray[idx])) if (idx % 2 == 0): time.sleep(10) print("============") # print(imgInfoArray) return imgInfoArray def startProcessing(self, pathToDir): fileNameArray = self.getAllImagePathFromDir(pathToDir) fileImageInfoArray = self.getImageInformationFromKhiena(fileNameArray) imgCounter = 0 for imageInfo in fileImageInfoArray: if (not imageInfo["source"]): continue imgInfo = imgUrlGetter.getImgInfoFromUrl(imageInfo["source"]) completeSourceText = '<a href="' + \ imageInfo["source"]+'"+>'+imageInfo["source"]+'</a>' print("img Path: "+imgInfo["imgLink"]) stringTags = self.getTagsString(imgInfo["tags"]) if (imageInfo["rating"]==0): stringTags += "safe, " elif (imageInfo["rating"]==1): stringTags += "questionable, " elif (imageInfo["rating"]==2): stringTags += "explicit, " print("RATING: ",imageInfo["rating"]) stringTags += "artist:"+imageInfo["artist"] similarity = imageInfo["similarity"] colorRowRect = "#f2ca7d" if similarity < 90: colorRowRect = "#f04747" btnName = "sendBtnName"+str(imgCounter) value = {"similarity": similarity, "source": completeSourceText, "localImgPath": imageInfo["localImgPath"], "remoteImgPath": imgInfo["imgLink"], "tags": stringTags, "colorRowRect": colorRowRect, "sendBtnName":btnName} # print(value) imgCounter+=1 QMetaObject.invokeMethod( self.view.rootObject(), "append", Q_ARG(QVariant, value)) #print(btnName) #sendButton = self.view.findChild(QObject, "sendBtnName0") #print(sendButton) #sendButton.clicked.connect(self.sendImgToGallery) #QMetaObject.invokeMethod( # self.view.rootObject(), "hideBusyIndicator") def on_qml_mouse_clicked(self): print('mouse clicked') def __init__(self): super().__init__() self.initUI() def initUI(self): self.view = QQuickView() self.view.setSource(QUrl('mainWin.qml')) self.view.rootContext().setContextProperty("ex",self) self.sortDirPath = "" openFolderButton = self.view.findChild(QObject, "toolButton") openFolderButton.clicked.connect(self.buttonClicked) selectSortDirButton = self.view.findChild(QObject, "selectSortDirButton") selectSortDirButton.clicked.connect(self.selectSortDirPath) child = self.view.findChild(QObject, "limgListModel") imageInfo = {"idshnik": 4, 'similarity': 80.859408, 'title': "Dragons' Journeyby Selianth", 'source': 'https://furrynetwork.com/artwork/1382822', 'artist': 'selianth', 'rating': 0, 'localImgPath': '/home/ /драконы-неразр-тест/425808117_107442_11461217378126189089.jpg'} colorRowRect = "#f2ca7d" value1 = {'similarity': 97.144509, 'source': '<a href="https://furrynetwork.com/artwork/341709">https://furrynetwork.com/artwork/341709</a>', 'localImgPath': '/home/ /драконы-неразр-тест/246938919_136836_11381943016272256370.jpg', 'remoteImgPath': 'https://d3gz42uwgl1r1y.cloudfront.net/ko/kodar/submission/2016/02/1dac4b544380d5874a518047f24b4eb2.jpg', 'tags': "artist:Kodar, any, dragon, fantasy, western", "colorRowRect": colorRowRect, "sendBtnName":"sendBtn0"} value = {"similarity": imageInfo["similarity"], "source": imageInfo["source"], "localImgPath": imageInfo["localImgPath"], } QMetaObject.invokeMethod( self.view.rootObject(), "append", Q_ARG(QVariant, value1)) self.view.show()
return animal.type() if role == self.SizeRole: return animal.size() return QVariant() def roleNames(self): return self._roles if __name__ == '__main__': import sys app = QGuiApplication(sys.argv) model = AnimalModel() model.addAnimal(Animal("Wolf", "Medium")) model.addAnimal(Animal("Polar bear", "Large")) model.addAnimal(Animal("Quoll", "Small")) view = QQuickView() view.setResizeMode(QQuickView.SizeRootObjectToView) ctxt = view.rootContext() ctxt.setContextProperty('myModel', model) view.setSource(QUrl('qrc:view.qml')) view.show() sys.exit(app.exec_())
class Selector(QWidget): def __init__(self, goal_context, config_path, experimentlogger): self.path = config_path super(Selector, self).__init__() self.ros_timer = QTimer() self.ros_timer.timeout.connect(self.ros_poller) self.setup_ui() self.AUTOEXECUTE = False self.ignore_move_events = False self.context = goal_context self.planner = None self.current_plan = None self.current_action_succeeded = False self.current_action_index = 0 self.current_menu_index = 0 self.task = None self.planner = planning.Planner(goal_context.init, self.path) self.ros_handler = None self.experiment_logger = experimentlogger self.init_menu() self.show_goal_ui() def ros_poller(self): # print "checking for ROS triggered changes in ROS event queue" if self.ros_handler and self.ros_handler.event_queue: # print "Processing events from ROS event queue. There are %d events" % len(self.ros_handler.event_queue) event = self.ros_handler.event_queue.pop(0) # print "popped ", str(event[0]) if "teleop" not in str(event[0]): event[0]( *event[1] ) # event is a pair (method,[params]) the asteriks unpacks parameters else: logger.debug( "HEY, don't teleoperate while actions are running: %s", str(event[0])) # print "executed ", str(event[0]) def setup_ui(self): self.view = QQuickView() self.view.setResizeMode(QQuickView.SizeRootObjectToView) container = QWidget.createWindowContainer(self.view) container.setFocusPolicy(Qt.StrongFocus) layout = QVBoxLayout() layout.addWidget(container) self.setLayout(layout) ctx = self.view.rootContext() ctx.setContextProperty("selector", self) ctx.setContextProperty("C", ui_constants) self.set_menu([]) self.set_plan([]) self.set_current(None) self.view.setSource(QUrl(self.path + 'ui.qml')) self.resize(800, 800) self.move(300, 300) self.setWindowTitle('Goal Planner GUI') self.setWindowIcon(QIcon('images/icon.png')) reload = QShortcut(QKeySequence("Ctrl+R"), self) reload.activated.connect(self.reload_ui) quit = QShortcut(QKeySequence("Ctrl+C"), self) quit.activated.connect(self.quit) quit = QShortcut(QKeySequence("Esc"), self) quit.activated.connect(self.quit) self.ros_timer.start(10) self.showMaximized() def test_populate(self): l = ["give", "grasp", "drop", "open", "close"] self.set_menu(l) def set_current(self, goal): ctx = self.view.rootContext() ctx.setContextProperty("currentGoal", QVariant(goal)) self.current = goal # do not free the list while it's in the model def set_menu(self, elems): ctx = self.view.rootContext() ctx.setContextProperty("selectionsModel", QVariant(elems)) self.elems = elems # do not free the list while it's in the model def set_plan(self, elems): ctx = self.view.rootContext() ctx.setContextProperty("planModel", QVariant(elems)) self.plans = elems # do not free the list while it's in the model def activate_loading(self): compute_image_focus = self.view.rootObject().findChild( QObject, "compute_image_focus") compute_image_focus.turnOn() def deactivate_loading(self): compute_image_focus = self.view.rootObject().findChild( QObject, "compute_image_focus") compute_image_focus.turnOff() @pyqtSlot() def goal_menu(self): class create_goal_menu(QThread): def __init__(self, instance): QThread.__init__(self) self.instance = instance def __del__(self): self.wait() def run(self): if constants.SHOW_FUNCTION_GOALS: self.instance.next_goals = goals.FunctionGoal.initial_goals( self.instance.context ) + goals.ActionGoal.initial_goals(self.instance.context) else: self.instance.next_goals = goals.ActionGoal.initial_goals( self.instance.context) # logger2.debug("Next goals: %s", map(str, self.next_goals)) self.instance.next_goals = [ g for g in self.instance.next_goals if self.instance.filter_goal(g) ] """ Shows the selection of options """ self.experiment_logger.log_performance( "Goal Menu shows next goals ...") self.create_goal_menu_thread = create_goal_menu(self) self.create_goal_menu_thread.finished.connect( self.create_goal_menu_done) self.create_goal_menu_thread.start() self.activate_loading() def create_goal_menu_done(self): items = [GoalMenuItem(g, -1) for g in self.next_goals] items.append(BackMenuItem()) self.current_goal = GOAL_MENU self.build_header(None) self.set_menu(items) self.experiment_logger.log_performance( "Goal Menu shows next goals done", True) self.create_goal_menu_thread = None self.deactivate_loading() @pyqtSlot() def action_menu(self): self.next_goals = goals.ExecActionGoal.initial_goals(self.context) items = [GoalMenuItem(g, -1) for g in self.next_goals] items.append(BackMenuItem()) self.current_goal = ACTION_MENU self.build_header(None) self.set_menu(items) @pyqtSlot(int) def select_goal(self, index): # logger2.debug("select goal") # image = self.view.rootObject().findChild(QObject, "compute_image") # image.turnOn() # self.experiment_logger.log_performance("user chose goal #%s" %index) #traceback.print_stack() if self.next_goals: self.display_goal(self.next_goals[index]) #self.experiment_logger.log_performance("goal #%s displayed" %index) # image.turnOff() @pyqtSlot(int) def select_action(self, index): if self.ignore_move_events: logger.debug( "aborting action execution because an action is already in progress" ) return #print self.current_action_index, index if not (self.current_action_index == index or self.current_action_index + 1 == index): logger.debug("Execute previous actions first!!!") return if self.current_plan and index < len(self.current_plan): if self.current_plan[ index].status == plans.ActionStatusEnum.EXECUTED: logger.debug("The action was already executed") return plan_node = self.current_plan[index] plan_node.status = plans.ActionStatusEnum.IN_PROGRESS self.display_status_text("Action is in progress") self.ignore_move_events = True self.ros_handler.execute_on_robot(plan_node.action, plan_node.arguments) items = [ PlanItem(pnode, self.context) for pnode in self.current_plan ] self.set_plan(items) planView = self.view.rootObject().findChild(QObject, "planView") planView.setProperty("currentIndex", index) self.current_action_index = index self.current_action_succeeded = False logger.debug("action selection of %s done, action executed" % index) # self.show_plan_ui() def action_executed(self): success = self.current_action_succeeded planView = self.view.rootObject().findChild(QObject, "planView") index = self.current_action_index logger.debug("current action index %d was executed. success = %s" % (index, success)) if success: plan_node = self.current_plan[index] if not self.planner.execute_action(plan_node.action, plan_node.arguments): plan_node.status = plans.ActionStatusEnum.FAILED logger.fatal( "\033[91mWarning warning warning, Planner found action to be infeasible... aborting\033[0m" ) #assert False self.current_plan[index].status = plans.ActionStatusEnum.EXECUTED #logger.debug("will refresh context now") #self.refresh_context(self.planner.problem) else: self.current_plan[ index].status = plans.ActionStatusEnum.UNSUCCESSFUL self.ignore_move_events = False logger.debug("select next action in plan GUI") planView = self.view.rootObject().findChild(QObject, "planView") items = [PlanItem(pnode, self.context) for pnode in self.current_plan] self.set_plan(items) if success and index + 1 < len(self.current_plan): planView.setProperty("currentIndex", index + 1) # select next plan step if self.AUTOEXECUTE: logger.debug("Auto-executing next") pui = self.view.rootObject().findChild(QObject, "planner_ui") pui.teleop_select() else: logger.debug("autoexecute ist turned off") self.refresh_context(self.planner.problem) else: planView.setProperty("currentIndex", index) # self.show_plan_ui() def handle_world_update(self): world = self.view.rootObject().findChild(QObject, "world_image") world.turn_world() self.init_menu() self.show_goal_ui() self.planner.set_state(self.context.init) # print("World change happened, world turning image displayed. Now I check the plan") # print("goal= ",self.goal) # self.plan(goal) @pyqtSlot() def back(self): if self.ignore_move_events and self.current_plan[ self. current_action_index].status == plans.ActionStatusEnum.IN_PROGRESS: #action is currently running -> abort it self.current_plan[ self. current_action_index].status = plans.ActionStatusEnum.UNSUCCESSFUL planView = self.view.rootObject().findChild(QObject, "planView") items = [ PlanItem(pnode, self.context) for pnode in self.current_plan ] self.set_plan(items) planView.setProperty("currentIndex", self.current_action_index) self.ros_handler.abort_current_action() self.ignore_move_events = False return if self.menu_stack: g = self.menu_stack.pop() self.current_goal = None if g == GOAL_MENU: self.goal_menu() elif g == ACTION_MENU: self.action_menu() else: self.display_goal(g) else: self.init_menu() self.show_goal_ui() @pyqtSlot() def choose(self): logger.debug("choose pressed") # self.menu_stack.pop() goal = self.current_goal if goal.is_unique(): if isinstance(goal, goals.ExecActionGoal): self.execute_action_goal(goal) else: self.plan(goal) return if self.current_goal is not None: self.menu_stack.append(self.current_goal) current_index = goal.arg_index(goal.get_current_arg()) logger.debug("index = %d", current_index) logger.debug( "\033[93m next from current goal----------\n----------\n--------\n-------" ) self.next_goals = [ g for g in self.current_goal.next_all() if not g.is_empty() and self.filter_goal(g) ] #self.next_goals = [g for g in self.current_goal.next() if not g.is_empty() and self.filter_goal(g)] logger.debug("\033[0m next_goals: %s", map(str, self.next_goals)) items = [GoalMenuItem(g, current_index) for g in self.next_goals] if self.menu_stack: items.append(ChooseMenuItem()) items.append(BackMenuItem()) self.build_header(goal) self.set_menu(items) def reload_ui(self): # print "reload_ui clear cache" self.view.engine().clearComponentCache() # print "reload_ui set menu" self.set_menu([]) # print "reload_ui set plan" self.set_plan([]) # print "reload_ui set current" self.set_current(None) if self.mode == MENU_MODE_GOAL: logger.debug("reload_ui show goal") self.show_goal_ui() else: logger.debug("reload_ui show plan") self.show_plan_ui() #print "reload_ui updatself.menu_stack.append(self.current_goal)e" self.repaint() @pyqtSlot() def quit(self): #pass # self.hide() sys.exit(0) # @pyqtSlot('QString') def display_status_text(self, displaytext): # print "about to display in statusbar >%s<" % displaytext bar = self.view.rootObject().findChild(QObject, "statusbar") # QMetaObject.invokeMethod(bar, "display", Qt.DirectConnection, Q_ARG(QVariant, displaytext)) #bar.display("%s" % displaytext) bar.display("") def action_help(self, action): help = "" if action == "go": help = "go [robot] [room], i.e., move the robot into a room" elif action == "approach": help = "approach [robot] [target base], i.e., move the robot to a target (furniture, flowerbed, humans) in the current room" elif action == "drop": help = "drop [robot] [object] [target], i.e., drop an object on a target (furniture, flowerbed, humans)" elif action == "arrange": help = "arrange [robot] [flower] [vase], i.e., arrange a flower in a vase" elif action == "open": help = "open [robot] [bottle], i.e., lets the robot open a bottle" elif action == "drink": help = "drink [human] [vessel] [content], i.e., the robot gives a drink in a vessel with the specified content to the human" elif action == "pour": help = "pour [from vessel] [to vessel] [content], i.e., the robot pours a liquid (3. parameter) from the first vessel into the second one" elif action == "give": help = "give [robot] [object] [human], i.e., the robot brings an object to the human" elif action == "grasp": help = "grasp [robot] [object], i.e., the robot grasps an object" elif action == "pick": help = "pick [robot] [flower] [vase], i.e., the robot picks a flower from a vase" else: help = "Unknown action " + action return help @pyqtSlot(int) def display_status(self, index): text = "Unknown Element with Index " + str(index) # print "Displaying ", text # print "Current goal -> ", self.current_goal # print "Current menustack -> ", self.menu_stack # print "current -> ", self.current # print "Mode -> ", self.mode self.current_menu_index = index if self.mode == MENU_MODE_GOAL: if self.current_goal: # print "ui.py/display_status debug dump of current goal:" # print self.current_goal.__class__ # if type(self.current_goal) is not int: # print "active = %s" % self.current_goal.arg_index # print "goal args= " # for arg in self.current_goal.args: # print str(arg) if (index < len(self.next_goals)): next_goal = self.next_goals[index] text = str(next_goal) if isinstance(next_goal, goals.ActionGoal): action = next_goal.action.name help = self.action_help(action) text = "Current: " + text + "\nHelp: " + help elif (index == len(self.next_goals)): text = "BACK" else: text = rootMenu[index].arg[0].text else: if index < len(self.current_plan): text = str(self.current_plan[index]) else: text = "Cannot display status of step %s in a plan of length %s" % ( index, len(self.current_plan)) # if self.current_goal and isinstance(self.current_goal, goals.ActionGoal): # action = self.current_goal.action.name # help = self.action_help(action) # text = "Current: " + text + "\nHelp: " + help self.display_status_text(text) self.experiment_logger.log_navigation(index, text) def init_menu(self): # logger2.debug("initializing menu") # image = self.view.rootObject().findChild(QObject, "compute_image") # image.turnOn() self.mode = MENU_MODE_GOAL self.menu_stack = [] self.current_goal = None self.build_header(None) ctx = self.view.rootContext() # root = self.view.rootObject().findChild(QObject, "rootMenu") logger.debug("set context property to root menu %s", rootMenu) # image.turnOff() ctx.setContextProperty("selectionsModel", rootMenu) self.set_menu(rootMenu) def show_goal_ui(self): self.mode = MENU_MODE_GOAL gui = self.view.rootObject().findChild(QObject, "goal_ui") pui = self.view.rootObject().findChild(QObject, "planner_ui") pui.setProperty("visible", False) gui.setProperty("visible", True) gui.setProperty("focus", True) def show_plan_ui(self): logger.debug("show plan ui in mode %s", self.mode) self.mode = MENU_MODE_PLAN gui = self.view.rootObject().findChild(QObject, "goal_ui") pui = self.view.rootObject().findChild(QObject, "planner_ui") gui.setProperty("visible", False) pui.setProperty("visible", True) pui.setProperty("focus", True) self.display_status(0) def add_image_provider(self, image_provider): self.image_provider = image_provider self.view.rootContext().engine().addImageProvider( 'ros_image_provider', self.image_provider) image_provider.connect_gui(self) # def change_focuspoint_color_srv(self): # widget = self.view.rootObject().findChild(QObject, 'crosshair') # def switch_to_video_view(self): # self.view.rootObject().switch_to_video_view() # def switch_to_main_view(self): # self.view.rootObject().switch_to_main_view() def build_header(self, goal): if goal is None or goal.__class__ == goals.GoalSpec: self.set_current(None) return self.set_current(CurrentGoalItem(goal)) def display_goal(self, goal): class display_goal_menu(QThread): def __init__(self, instance, goal): QThread.__init__(self) self.instance = instance self.goal = goal def __del__(self): self.wait() def run(self): if self.goal.is_unique(): if isinstance(self.goal, goals.ExecActionGoal): self.instance.execute_action_goal(self.goal) else: self.instance.plan(self.goal, False) self.instance.show_plan_flag = True return self.instance.current_index = self.goal.arg_index( self.goal.get_current_arg()) self.instance.next_goals = [ g for g in self.instance.current_goal.next_flattened() if not g.is_empty() and self.instance.filter_goal(g) ] logger.debug("will display goal %s", str(goal)) self.experiment_logger.log_performance("ui.show_goal_ui display goal") # #CalledGoals.Instance().call_goal(goal) # if goal.is_unique(): # if isinstance(goal, goals.ExecActionGoal): # self.execute_action_goal(goal) # else: # self.plan(goal) # return # # if self.current_goal is not None: # self.menu_stack.append(self.current_goal) # # self.current_goal = goal # self.current_index = goal.arg_index(goal.get_current_arg()) #tmp_goals = [(-CalledGoals.Instance().get_calls(g)*100+i, g) for i, g in enumerate(self.current_goal.next_flattened()) if not g.is_empty() and self.filter_goal(g)] #tmp_goals.sort() #self.next_goals = [g for _, g in tmp_goals] if self.current_goal is not None: self.menu_stack.append(self.current_goal) self.current_goal = goal self.show_plan_flag = False self.display_goal_menu_thread = display_goal_menu(self, goal) self.display_goal_menu_thread.finished.connect( self.display_goal_menu_done) self.display_goal_menu_thread.start() self.activate_loading() # self.next_goals = [g for g in self.current_goal.next_flattened() if not g.is_empty() and self.filter_goal(g)] def display_goal_menu_done(self): if self.show_plan_flag: self.plan_update_gui() if self.experiment_logger.autoperform: self.quit() items = [GoalMenuItem(g, self.current_index) for g in self.next_goals] if self.menu_stack: #items.append(ChooseMenuItem()) items.append(BackMenuItem()) self.build_header(self.current_goal) self.set_menu(items) self.experiment_logger.log_performance("ui.display_goal_menu_done", True) self.display_goal_menu_thread = None self.deactivate_loading() def execute_action_goal(self, action_goal): action = action_goal.action problem = self.context.problem #instantiate an action that matches the selected action and execute it valid_args = action_goal.get_matches() action_args = [] for aarg in action_goal.action.args: if aarg in action_goal.used_arguments: i = action_goal.used_arguments.index(aarg) objects = set(tup[i] for tup in valid_args) # print self.args[i], map(str, objects) else: objects = list(problem.get_all_objects(aarg.type)) action_args.append(objects) inst_function = action.get_inst_func(self.context.init) args = None for mapping in action.smart_instantiate(inst_function, action.args, action_args, problem): args = [mapping[a] for a in action.args] break self.planner.execute_action(action, args) #self.refresh_context(self.planner.problem) self.menu_stack = [] self.action_menu() def refresh_context(self, problem, refs=None): if refs is None: # use old references instead refs = self.context.refs self.context = goals.GoalContext(problem, refs) def plan_update_gui(self): items = [PlanItem(pnode, self.context) for pnode in self.current_plan] self.set_plan(items) self.show_plan_ui() def plan(self, goal, update_gui=True): self.menu_stack = [] self.current_goal = None self.current_action_index = 0 assert self.planner is not None self.planner.find_plan(goal) self.current_plan = self.planner.get_plan() if update_gui: self.plan_update_gui() def filter_goal(self, goal): if goal.is_universal(): return False return True
if __name__ == '__main__': #=========================================================================== # import os # import sys # app = QGuiApplication(sys.argv) # engine = QQmlApplicationEngine() # context = engine.rootContext() # game = Game() # context.setContextProperty('loginCh', game) # qml_filename = os.path.join('qml/main.qml') # engine.load(qml_filename) # sys.exit(app.exec_()) #=========================================================================== myApp = QApplication(sys.argv) # Create a label and set its properties appLabel = QQuickView() game = Game() user = User() appLabel.rootContext().setContextProperty('loginCh', game) appLabel.rootContext().setContextProperty('user', user) appLabel.setSource(QUrl('qml/Loginw.qml')) # Show the Label appLabel.show() # Execute the Application and Exit myApp.exec_() sys.exit()
appLabel = QQuickView() appLabel.setSource(QUrl('main.qml')) #appLabel.load(QUrl('main2.qml')) # Show the Label appLabel.show() # Create a QML engine. engine = QQmlEngine() # Initialize PhotoBoothEngine. pbengine = PhotoBoothEngine() pbengine.on_status.connect(appLabel.rootObject().status) pbengine.on_update_filter_preview.connect(appLabel.rootObject().updateImageFilterPreview) appLabel.rootContext().setContextProperty('pbengine', pbengine) # Create a component factory and load the QML script. print("Hello") component = QQmlComponent(appLabel.engine()) component.loadUrl(QUrl('TextStatusFly.qml')) print("Hello2") asdf = component.create(appLabel.rootContext()) print("Hello3") asdf.setParentItem(appLabel.rootObject()) asdf.setParent(appLabel.rootObject()) print("Hello4") #asdf.setProperty("targetX", 100)
class Application(QApplication): """Main Nuxeo Drive application controlled by a system tray icon + menu""" icon = QIcon(str(find_icon("app_icon.svg"))) icons: Dict[str, QIcon] = {} icon_state = None use_light_icons = None filters_dlg: Optional[FiltersDialog] = None _delegator: Optional["NotificationDelegator"] = None tray_icon: DriveSystrayIcon def __init__(self, manager: "Manager", *args: Any) -> None: super().__init__(list(*args)) self.manager = manager self.osi = self.manager.osi self.setWindowIcon(self.icon) self.setApplicationName(APP_NAME) self._init_translator() self.setQuitOnLastWindowClosed(False) self.ask_for_metrics_approval() self._conflicts_modals: Dict[str, bool] = dict() self.current_notification: Optional[Notification] = None self.default_tooltip = APP_NAME font = QFont("Helvetica, Arial, sans-serif", 12) self.setFont(font) self.ratio = sqrt(QFontMetricsF(font).height() / 12) self.init_gui() self.manager.dropEngine.connect(self.dropped_engine) self.setup_systray() self.manager.reloadIconsSet.connect(self.load_icons_set) # Direct Edit self.manager.direct_edit.directEditConflict.connect(self._direct_edit_conflict) self.manager.direct_edit.directEditError.connect(self._direct_edit_error) # Check if actions is required, separate method so it can be overridden self.init_checks() # Setup notification center for macOS if MAC: self._setup_notification_center() # Application update self.manager.updater.appUpdated.connect(self.quit) self.manager.updater.serverIncompatible.connect(self._server_incompatible) self.manager.updater.wrongChannel.connect(self._wrong_channel) # Display release notes on new version if self.manager.old_version != self.manager.version: self.show_release_notes(self.manager.version) # Listen for nxdrive:// sent by a new instance self.init_nxdrive_listener() # Connect this slot last so the other slots connected # to self.aboutToQuit can run beforehand. self.aboutToQuit.connect(self.manager.stop) @if_frozen def add_qml_import_path(self, view: QQuickView) -> None: """ Manually set the path to the QML folder to fix errors with unicode paths. This is needed only on Windows when packaged with Nuitka. """ if Options.freezer != "nuitka": return qml_dir = Options.res_dir.parent / "PyQt5" / "Qt" / "qml" log.debug(f"Setting QML import path for {view} to {qml_dir!r}") view.engine().addImportPath(str(qml_dir)) def init_gui(self) -> None: self.api = QMLDriveApi(self) self.conflicts_model = FileModel() self.errors_model = FileModel() self.engine_model = EngineModel(self) self.action_model = ActionModel() self.file_model = FileModel() self.ignoreds_model = FileModel() self.language_model = LanguageModel() self.add_engines(list(self.manager._engines.values())) self.engine_model.statusChanged.connect(self.update_status) self.language_model.addLanguages(Translator.languages()) flags = Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint if WINDOWS: self.conflicts_window = QQuickView() self.add_qml_import_path(self.conflicts_window) self.conflicts_window.setMinimumWidth(550) self.conflicts_window.setMinimumHeight(600) self.settings_window = QQuickView() self.add_qml_import_path(self.settings_window) self.settings_window.setMinimumWidth(640) self.settings_window.setMinimumHeight(520) self.systray_window = SystrayWindow() self.add_qml_import_path(self.systray_window) self._fill_qml_context(self.conflicts_window.rootContext()) self._fill_qml_context(self.settings_window.rootContext()) self._fill_qml_context(self.systray_window.rootContext()) self.systray_window.rootContext().setContextProperty( "systrayWindow", self.systray_window ) self.conflicts_window.setSource( QUrl.fromLocalFile(str(find_resource("qml", "Conflicts.qml"))) ) self.settings_window.setSource( QUrl.fromLocalFile(str(find_resource("qml", "Settings.qml"))) ) self.systray_window.setSource( QUrl.fromLocalFile(str(find_resource("qml", "Systray.qml"))) ) flags |= Qt.Popup else: self.app_engine = QQmlApplicationEngine() self._fill_qml_context(self.app_engine.rootContext()) self.app_engine.load( QUrl.fromLocalFile(str(find_resource("qml", "Main.qml"))) ) root = self.app_engine.rootObjects()[0] self.conflicts_window = root.findChild(QQuickWindow, "conflictsWindow") self.settings_window = root.findChild(QQuickWindow, "settingsWindow") self.systray_window = root.findChild(SystrayWindow, "systrayWindow") if LINUX: flags |= Qt.Drawer self.systray_window.setFlags(flags) self.manager.newEngine.connect(self.add_engines) self.manager.initEngine.connect(self.add_engines) self.manager.dropEngine.connect(self.remove_engine) self._window_root(self.conflicts_window).changed.connect(self.refresh_conflicts) self._window_root(self.systray_window).appUpdate.connect(self.api.app_update) self._window_root(self.systray_window).getLastFiles.connect(self.get_last_files) self.api.setMessage.connect(self._window_root(self.settings_window).setMessage) if self.manager.get_engines(): current_uid = self.engine_model.engines_uid[0] self.get_last_files(current_uid) self.update_status(self.manager._engines[current_uid]) self.manager.updater.updateAvailable.connect( self._window_root(self.systray_window).updateAvailable ) self.manager.updater.updateProgress.connect( self._window_root(self.systray_window).updateProgress ) @pyqtSlot(Action) def action_started(self, action: Action) -> None: self.refresh_actions() @pyqtSlot(Action) def action_progressing(self, action: Action) -> None: self.action_model.set_progress(action.export()) @pyqtSlot(Action) def action_done(self, action: Action) -> None: self.refresh_actions() def add_engines(self, engines: Union[Engine, List[Engine]]) -> None: if not engines: return engines = engines if isinstance(engines, list) else [engines] for engine in engines: self.engine_model.addEngine(engine.uid) def remove_engine(self, uid: str) -> None: self.engine_model.removeEngine(uid) def _fill_qml_context(self, context: QQmlContext) -> None: """ Fill the context of a QML element with the necessary resources. """ context.setContextProperty("ConflictsModel", self.conflicts_model) context.setContextProperty("ErrorsModel", self.errors_model) context.setContextProperty("EngineModel", self.engine_model) context.setContextProperty("ActionModel", self.action_model) context.setContextProperty("FileModel", self.file_model) context.setContextProperty("IgnoredsModel", self.ignoreds_model) context.setContextProperty("languageModel", self.language_model) context.setContextProperty("api", self.api) context.setContextProperty("application", self) context.setContextProperty("currentLanguage", self.current_language()) context.setContextProperty("manager", self.manager) context.setContextProperty("osi", self.osi) context.setContextProperty("updater", self.manager.updater) context.setContextProperty("ratio", self.ratio) context.setContextProperty("update_check_delay", Options.update_check_delay) context.setContextProperty("isFrozen", Options.is_frozen) context.setContextProperty("WINDOWS", WINDOWS) context.setContextProperty("tl", Translator._singleton) context.setContextProperty( "nuxeoVersionText", f"{APP_NAME} {self.manager.version}" ) metrics = self.manager.get_metrics() versions = ( f'Python {metrics["python_version"]}, ' f'Qt {metrics["qt_version"]}, ' f'SIP {metrics["sip_version"]}' ) if Options.system_wide: versions += " [admin]" context.setContextProperty("modulesVersionText", versions) colors = { "darkBlue": "#1F28BF", "nuxeoBlue": "#0066FF", "lightBlue": "#00ADED", "teal": "#73D2CF", "purple": "#8400FF", "red": "#C02828", "orange": "#FF9E00", "darkGray": "#495055", "mediumGray": "#7F8284", "lightGray": "#BCBFBF", "lighterGray": "#F5F5F5", } for name, value in colors.items(): context.setContextProperty(name, value) def _window_root(self, window): if WINDOWS: return window.rootObject() return window def translate(self, message: str, values: List[Any] = None) -> str: return Translator.get(message, values) def _show_window(self, window: QWindow) -> None: window.show() window.raise_() window.requestActivate() def _init_translator(self) -> None: locale = Options.force_locale or Options.locale Translator(find_resource("i18n"), self.manager.get_config("locale", locale)) # Make sure that a language change changes external values like # the text in the contextual menu Translator.on_change(self._handle_language_change) # Trigger it now self.osi.register_contextual_menu() self.installTranslator(Translator._singleton) @pyqtSlot(str, Path, str) def _direct_edit_conflict(self, filename: str, ref: Path, digest: str) -> None: log.debug(f"Entering _direct_edit_conflict for {filename!r} / {ref!r}") try: if filename in self._conflicts_modals: log.debug(f"Filename already in _conflicts_modals: {filename!r}") return log.debug(f"Putting filename in _conflicts_modals: {filename!r}") self._conflicts_modals[filename] = True msg = QMessageBox() msg.setInformativeText( Translator.get("DIRECT_EDIT_CONFLICT_MESSAGE", [short_name(filename)]) ) overwrite = msg.addButton( Translator.get("DIRECT_EDIT_CONFLICT_OVERWRITE"), QMessageBox.AcceptRole ) msg.addButton(Translator.get("CANCEL"), QMessageBox.RejectRole) msg.setIcon(QMessageBox.Warning) msg.exec_() if msg.clickedButton() == overwrite: self.manager.direct_edit.force_update(ref, digest) del self._conflicts_modals[filename] except: log.exception( f"Error while displaying Direct Edit conflict modal dialog for {filename!r}" ) @pyqtSlot(str, list) def _direct_edit_error(self, message: str, values: List[str]) -> None: """ Display a simple Direct Edit error message. """ msg_text = self.translate(message, values) log.warning(f"DirectEdit error message: '{msg_text}', values={values}") msg = QMessageBox() msg.setWindowTitle(f"Direct Edit - {APP_NAME}") msg.setWindowIcon(self.icon) msg.setIcon(QMessageBox.Warning) msg.setTextFormat(Qt.RichText) msg.setText(msg_text) msg.exec_() @pyqtSlot() def _root_deleted(self) -> None: engine = self.sender() log.info(f"Root has been deleted for engine: {engine.uid}") msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setWindowIcon(self.icon) msg.setText(Translator.get("DRIVE_ROOT_DELETED", [engine.local_folder])) recreate = msg.addButton( Translator.get("DRIVE_ROOT_RECREATE"), QMessageBox.AcceptRole ) disconnect = msg.addButton( Translator.get("DRIVE_ROOT_DISCONNECT"), QMessageBox.RejectRole ) msg.exec_() res = msg.clickedButton() if res == disconnect: self.manager.unbind_engine(engine.uid) elif res == recreate: engine.reinit() engine.start() @pyqtSlot() def _no_space_left(self) -> None: msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowIcon(self.icon) msg.setText(Translator.get("NO_SPACE_LEFT_ON_DEVICE")) msg.addButton(Translator.get("OK"), QMessageBox.AcceptRole) msg.exec_() @pyqtSlot(Path) def _root_moved(self, new_path: Path) -> None: engine = self.sender() log.info(f"Root has been moved for engine: {engine.uid} to {new_path!r}") info = [engine.local_folder, str(new_path)] msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setWindowIcon(self.icon) msg.setText(Translator.get("DRIVE_ROOT_MOVED", info)) move = msg.addButton( Translator.get("DRIVE_ROOT_UPDATE"), QMessageBox.AcceptRole ) recreate = msg.addButton( Translator.get("DRIVE_ROOT_RECREATE"), QMessageBox.AcceptRole ) disconnect = msg.addButton( Translator.get("DRIVE_ROOT_DISCONNECT"), QMessageBox.RejectRole ) msg.exec_() res = msg.clickedButton() if res == disconnect: self.manager.unbind_engine(engine.uid) elif res == recreate: engine.reinit() engine.start() elif res == move: engine.set_local_folder(new_path) engine.start() def confirm_deletion(self, path: Path) -> DelAction: msg = QMessageBox() msg.setIcon(QMessageBox.Question) msg.setWindowIcon(self.icon) cb = QCheckBox(Translator.get("DONT_ASK_AGAIN")) msg.setCheckBox(cb) mode = self.manager.get_deletion_behavior() unsync = None if mode is DelAction.DEL_SERVER: descr = "DELETION_BEHAVIOR_CONFIRM_DELETE" confirm_text = "DELETE_FOR_EVERYONE" unsync = msg.addButton( Translator.get("JUST_UNSYNC"), QMessageBox.RejectRole ) elif mode is DelAction.UNSYNC: descr = "DELETION_BEHAVIOR_CONFIRM_UNSYNC" confirm_text = "UNSYNC" msg.setText( Translator.get(descr, [str(path), Translator.get("SELECT_SYNC_FOLDERS")]) ) msg.addButton(Translator.get("CANCEL"), QMessageBox.RejectRole) confirm = msg.addButton(Translator.get(confirm_text), QMessageBox.AcceptRole) msg.exec_() res = msg.clickedButton() if cb.isChecked(): self.manager._dao.store_bool("show_deletion_prompt", False) if res == confirm: return mode if res == unsync: msg = QMessageBox() msg.setIcon(QMessageBox.Question) msg.setWindowIcon(self.icon) msg.setText(Translator.get("DELETION_BEHAVIOR_SWITCH")) msg.addButton(Translator.get("NO"), QMessageBox.RejectRole) confirm = msg.addButton(Translator.get("YES"), QMessageBox.AcceptRole) msg.exec_() res = msg.clickedButton() if res == confirm: self.manager.set_deletion_behavior(DelAction.UNSYNC) return DelAction.UNSYNC return DelAction.ROLLBACK @pyqtSlot(Path) def _doc_deleted(self, path: Path) -> None: engine: Engine = self.sender() mode = self.confirm_deletion(path) if mode is DelAction.ROLLBACK: # Re-sync the document engine.rollback_delete(path) else: # Delete or filter out the document engine.delete_doc(path, mode) @pyqtSlot(Path, Path) def _file_already_exists(self, oldpath: Path, newpath: Path) -> None: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setWindowIcon(self.icon) msg.setText(Translator.get("FILE_ALREADY_EXISTS", values=[str(oldpath)])) replace = msg.addButton(Translator.get("REPLACE"), QMessageBox.AcceptRole) msg.addButton(Translator.get("CANCEL"), QMessageBox.RejectRole) msg.exec_() if msg.clickedButton() == replace: oldpath.unlink() normalize_event_filename(newpath) else: newpath.unlink() @pyqtSlot(object) def dropped_engine(self, engine: "Engine") -> None: # Update icon in case the engine dropped was syncing self.change_systray_icon() @pyqtSlot() def change_systray_icon(self) -> None: # Update status has the precedence over other ones if self.manager.updater.status not in ( UPDATE_STATUS_UNAVAILABLE_SITE, UPDATE_STATUS_UP_TO_DATE, ): self.set_icon_state("update") return syncing = conflict = False engines = self.manager.get_engines() invalid_credentials = paused = offline = True for engine in engines.values(): syncing |= engine.is_syncing() invalid_credentials &= engine.has_invalid_credentials() paused &= engine.is_paused() offline &= engine.is_offline() conflict |= bool(engine.get_conflicts()) if offline: new_state = "error" Action(Translator.get("OFFLINE")) elif invalid_credentials: new_state = "error" Action(Translator.get("AUTH_EXPIRED")) elif not engines: new_state = "disabled" Action.finish_action() elif paused: new_state = "paused" Action.finish_action() elif syncing: new_state = "syncing" elif conflict: new_state = "conflict" else: new_state = "idle" Action.finish_action() self.set_icon_state(new_state) def refresh_conflicts(self, uid: str) -> None: """ Update the content of the conflicts/errors window. """ self.conflicts_model.empty() self.errors_model.empty() self.ignoreds_model.empty() self.conflicts_model.addFiles(self.api.get_conflicts(uid)) self.errors_model.addFiles(self.api.get_errors(uid)) self.ignoreds_model.addFiles(self.api.get_unsynchronizeds(uid)) @pyqtSlot() def show_conflicts_resolution(self, engine: "Engine") -> None: """ Display the conflicts/errors window. """ self.refresh_conflicts(engine.uid) self._window_root(self.conflicts_window).setEngine.emit(engine.uid) self.conflicts_window.show() self.conflicts_window.requestActivate() @pyqtSlot() def show_settings(self, section: str = "General") -> None: sections = {"General": 0, "Accounts": 1, "About": 2} self._window_root(self.settings_window).setSection.emit(sections[section]) self.settings_window.show() self.settings_window.requestActivate() @pyqtSlot() def show_systray(self) -> None: icon = self.tray_icon.geometry() if not icon or icon.isEmpty(): # On Ubuntu it's likely we can't retrieve the geometry. # We're simply displaying the systray in the top right corner. screen = self.desktop().screenGeometry() pos_x = screen.right() - self.systray_window.width() - 20 pos_y = 30 else: dpi_ratio = self.primaryScreen().devicePixelRatio() if WINDOWS else 1 pos_x = max( 0, (icon.x() + icon.width()) / dpi_ratio - self.systray_window.width() ) pos_y = icon.y() / dpi_ratio - self.systray_window.height() if pos_y < 0: pos_y = (icon.y() + icon.height()) / dpi_ratio self.systray_window.setX(pos_x) self.systray_window.setY(pos_y) self.systray_window.show() self.systray_window.raise_() @pyqtSlot() def hide_systray(self): self.systray_window.hide() @pyqtSlot() def open_help(self) -> None: self.manager.open_help() @pyqtSlot() def destroyed_filters_dialog(self) -> None: self.filters_dlg = None @pyqtSlot() def show_filters(self, engine: "Engine") -> None: if self.filters_dlg: self.filters_dlg.close() self.filters_dlg = None self.filters_dlg = FiltersDialog(self, engine) self.filters_dlg.destroyed.connect(self.destroyed_filters_dialog) # Close the settings window at the same time of the filters one if hasattr(self, "close_settings_too"): self.filters_dlg.destroyed.connect(self.settings_window.close) delattr(self, "close_settings_too") self.filters_dlg.show() self._show_window(self.settings_window) @pyqtSlot(str, object) def _open_authentication_dialog( self, url: str, callback_params: Dict[str, str] ) -> None: self.api._callback_params = callback_params if Options.is_frozen: """ Authenticate through the browser. This authentication requires the server's Nuxeo Drive addon to include NXP-25519. Instead of opening the server's login page in a WebKit view through the app, it opens in the browser and retrieves the login token by opening an nxdrive:// URL. """ self.manager.open_local_file(url) else: self._web_auth_not_frozen(url) def _web_auth_not_frozen(self, url: str) -> None: """ Open a dialog box to fill the credentials. Then a request will be done using the Python client to get a token. This is used when the application is not frozen as there is no custom protocol handler in this case. """ from PyQt5.QtWidgets import QLineEdit from nuxeo.client import Nuxeo dialog = QDialog() dialog.setWindowTitle(self.translate("WEB_AUTHENTICATION_WINDOW_TITLE")) dialog.setWindowIcon(self.icon) dialog.resize(250, 100) layout = QVBoxLayout() username = QLineEdit("Administrator", parent=dialog) password = QLineEdit("Administrator", parent=dialog) password.setEchoMode(QLineEdit.Password) layout.addWidget(username) layout.addWidget(password) def auth() -> None: """Retrieve a token and create the account.""" user = str(username.text()) pwd = str(password.text()) nuxeo = Nuxeo( host=url, auth=(user, pwd), proxies=self.manager.proxy.settings(url=url), verify=Options.ca_bundle or not Options.ssl_no_verify, ) try: token = nuxeo.client.request_auth_token( device_id=self.manager.device_id, app_name=APP_NAME, permission=TOKEN_PERMISSION, device=get_device(), ) except Exception as exc: log.error(f"Connection error: {exc}") token = "" finally: del nuxeo # Check we have a token and not a HTML response if "\n" in token: token = "" self.api.handle_token(token, user) dialog.close() buttons = QDialogButtonBox() buttons.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) buttons.accepted.connect(auth) buttons.rejected.connect(dialog.close) layout.addWidget(buttons) dialog.setLayout(layout) dialog.exec_() @pyqtSlot(object) def _connect_engine(self, engine: "Engine") -> None: engine.syncStarted.connect(self.change_systray_icon) engine.syncCompleted.connect(self.change_systray_icon) engine.invalidAuthentication.connect(self.change_systray_icon) engine.syncSuspended.connect(self.change_systray_icon) engine.syncResumed.connect(self.change_systray_icon) engine.offline.connect(self.change_systray_icon) engine.online.connect(self.change_systray_icon) engine.rootDeleted.connect(self._root_deleted) engine.rootMoved.connect(self._root_moved) engine.docDeleted.connect(self._doc_deleted) engine.fileAlreadyExists.connect(self._file_already_exists) engine.noSpaceLeftOnDevice.connect(self._no_space_left) self.change_systray_icon() def init_checks(self) -> None: for engine in self.manager.get_engines().values(): self._connect_engine(engine) self.manager.newEngine.connect(self._connect_engine) self.manager.notification_service.newNotification.connect( self._new_notification ) self.manager.notification_service.triggerNotification.connect( self._handle_notification_action ) self.manager.updater.updateAvailable.connect(self._update_notification) self.manager.updater.noSpaceLeftOnDevice.connect(self._no_space_left) if not self.manager.get_engines(): self.show_settings() else: for engine in self.manager.get_engines().values(): # Prompt for settings if needed if engine.has_invalid_credentials(): self.show_settings("Accounts") # f"Account_{engine.uid}" break self.manager.start() @pyqtSlot() @if_frozen def _update_notification(self) -> None: self.change_systray_icon() # Display a notification status, version = self.manager.updater.status, self.manager.updater.version msg = ("AUTOUPDATE_UPGRADE", "AUTOUPDATE_DOWNGRADE")[ status == UPDATE_STATUS_INCOMPATIBLE_SERVER ] description = Translator.get(msg, [version]) flags = Notification.FLAG_BUBBLE | Notification.FLAG_UNIQUE if LINUX: description += " " + Translator.get("AUTOUPDATE_MANUAL") flags |= Notification.FLAG_SYSTRAY log.warning(description) notification = Notification( uuid="AutoUpdate", flags=flags, title=Translator.get("NOTIF_UPDATE_TITLE", [version]), description=description, ) self.manager.notification_service.send_notification(notification) @pyqtSlot() def _server_incompatible(self) -> None: version = self.manager.version downgrade_version = self.manager.updater.version or "" msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowIcon(self.icon) msg.setText(Translator.get("SERVER_INCOMPATIBLE", [version, downgrade_version])) if downgrade_version: msg.addButton( Translator.get("CONTINUE_USING", [version]), QMessageBox.RejectRole ) downgrade = msg.addButton( Translator.get("DOWNGRADE_TO", [downgrade_version]), QMessageBox.AcceptRole, ) else: msg.addButton(Translator.get("CONTINUE"), QMessageBox.RejectRole) msg.exec_() res = msg.clickedButton() if downgrade_version and res == downgrade: self.manager.updater.update(downgrade_version) @pyqtSlot() def _wrong_channel(self) -> None: if self.manager.prompted_wrong_channel: log.debug( "Not prompting for wrong channel, already showed it since startup" ) return self.manager.prompted_wrong_channel = True version = self.manager.version downgrade_version = self.manager.updater.version or "" version_channel = self.manager.updater.get_version_channel(version) current_channel = self.manager.get_update_channel() msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowIcon(self.icon) msg.setText( Translator.get("WRONG_CHANNEL", [version, version_channel, current_channel]) ) switch_channel = msg.addButton( Translator.get("USE_CHANNEL", [version_channel]), QMessageBox.AcceptRole ) downgrade = msg.addButton( Translator.get("DOWNGRADE_TO", [downgrade_version]), QMessageBox.AcceptRole ) msg.exec_() res = msg.clickedButton() if downgrade_version and res == downgrade: self.manager.updater.update(downgrade_version) elif res == switch_channel: self.manager.set_update_channel(version_channel) @pyqtSlot() def message_clicked(self) -> None: if self.current_notification: self.manager.notification_service.trigger_notification( self.current_notification.uid ) def _setup_notification_center(self) -> None: if not self._delegator: self._delegator = NotificationDelegator.alloc().init() if self._delegator: self._delegator._manager = self.manager setup_delegator(self._delegator) @pyqtSlot(object) def _new_notification(self, notif: Notification) -> None: if not notif.is_bubble(): return if self._delegator is not None: # Use notification center from ..osi.darwin.pyNotificationCenter import notify user_info = {"uuid": notif.uid} if notif.uid else None return notify(notif.title, "", notif.description, user_info=user_info) icon = QSystemTrayIcon.Information if notif.level == Notification.LEVEL_WARNING: icon = QSystemTrayIcon.Warning elif notif.level == Notification.LEVEL_ERROR: icon = QSystemTrayIcon.Critical self.current_notification = notif self.tray_icon.showMessage(notif.title, notif.description, icon, 10000) @pyqtSlot(str, str) def _handle_notification_action(self, action: str, engine_uid: str) -> None: func = getattr(self.api, action, None) if not func: log.error(f"Action {action}() is not defined in {self.api}") return func(engine_uid) def set_icon_state(self, state: str, force: bool = False) -> bool: """ Execute systray icon change operations triggered by state change. The synchronization thread can update the state info but cannot directly call QtGui widget methods. This should be executed by the main thread event loop, hence the delegation to this method that is triggered by a signal to allow for message passing between the 2 threads. Return True of the icon has changed state or if force is True. """ if not force and self.icon_state == state: # Nothing to update return False self.tray_icon.setToolTip(self.get_tooltip()) self.tray_icon.setIcon(self.icons[state]) self.icon_state = state return True def get_tooltip(self) -> str: actions = Action.get_actions() if not actions: return self.default_tooltip # Display only the first action for now for action in actions.values(): if action and action.type and not action.type.startswith("_"): break else: return self.default_tooltip return f"{self.default_tooltip} - {action!r}" @if_frozen def show_release_notes(self, version: str) -> None: """ Display release notes of a given version. """ channel = self.manager.get_update_channel() log.info(f"Showing release notes, version={version!r} channel={channel}") # For now, we do care about beta only if channel != "beta": return url = ( "https://api.github.com/repos/nuxeo/nuxeo-drive" f"/releases/tags/release-{version}" ) if channel != "release": version += f" {channel}" try: # No need for the `verify` kwarg here as GitHub will never use a bad certificate. with requests.get(url) as resp: data = resp.json() html = markdown(data["body"]) except Exception: log.warning(f"[{version}] Release notes retrieval error") return dialog = QDialog() dialog.setWindowTitle(f"{APP_NAME} {version} - Release notes") dialog.setWindowIcon(self.icon) dialog.resize(600, 400) notes = QTextEdit() notes.setStyleSheet("background-color: #eee; border: none;") notes.setReadOnly(True) notes.setHtml(html) buttons = QDialogButtonBox() buttons.setStandardButtons(QDialogButtonBox.Ok) buttons.clicked.connect(dialog.accept) layout = QVBoxLayout() layout.addWidget(notes) layout.addWidget(buttons) dialog.setLayout(layout) dialog.exec_() def accept_unofficial_ssl_cert(self, hostname: str) -> bool: """Ask the user to bypass the SSL certificate verification.""" from ..utils import get_certificate_details def signature(sig: str) -> str: """ Format the certificate signature. >>> signature("0F4019D1E6C52EF9A3A929B6D5613816") 0f:40:19:d1:e6:c5:2e:f9:a3:a9:29:b6:d5:61:38:16 """ from textwrap import wrap return str.lower(":".join(wrap(sig, 2))) cert = get_certificate_details(hostname=hostname) if not cert: return False subject = [ f"<li>{details[0][0]}: {details[0][1]}</li>" for details in sorted(cert["subject"]) ] issuer = [ f"<li>{details[0][0]}: {details[0][1]}</li>" for details in sorted(cert["issuer"]) ] urls = [ f"<li><a href='{details}'>{details}</a></li>" for details in cert["caIssuers"] ] sig = f"<code><small>{signature(cert['serialNumber'])}</small></code>" message = f""" <h2>{Translator.get("SSL_CANNOT_CONNECT", [hostname])}</h2> <p style="color:red">{Translator.get("SSL_HOSTNAME_ERROR")}</p> <h2>{Translator.get("SSL_CERTIFICATE")}</h2> <ul> {"".join(subject)} <li style="margin-top: 10px;">{Translator.get("SSL_SERIAL_NUMBER")} {sig}</li> <li style="margin-top: 10px;">{Translator.get("SSL_DATE_FROM")} {cert["notBefore"]}</li> <li>{Translator.get("SSL_DATE_EXPIRATION")} {cert["notAfter"]}</li> </ul> <h2>{Translator.get("SSL_ISSUER")}</h2> <ul style="list-style-type:square;">{"".join(issuer)}</ul> <h2>{Translator.get("URL")}</h2> <ul>{"".join(urls)}</ul> """ dialog = QDialog() dialog.setWindowTitle(Translator.get("SSL_UNTRUSTED_CERT_TITLE")) dialog.setWindowIcon(self.icon) dialog.resize(600, 650) notes = QTextEdit() notes.setReadOnly(True) notes.setHtml(message) continue_with_bad_ssl_cert = False def accept() -> None: nonlocal continue_with_bad_ssl_cert continue_with_bad_ssl_cert = True dialog.accept() buttons = QDialogButtonBox() buttons.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.button(QDialogButtonBox.Ok).setEnabled(False) buttons.accepted.connect(accept) buttons.rejected.connect(dialog.close) def bypass_triggered(state: int) -> None: """Enable the OK button only when the checkbox is checked.""" buttons.button(QDialogButtonBox.Ok).setEnabled(bool(state)) bypass = QCheckBox(Translator.get("SSL_TRUST_ANYWAY")) bypass.stateChanged.connect(bypass_triggered) layout = QVBoxLayout() layout.addWidget(notes) layout.addWidget(bypass) layout.addWidget(buttons) dialog.setLayout(layout) dialog.exec_() return continue_with_bad_ssl_cert def show_metadata(self, path: Path) -> None: self.manager.ctx_edit_metadata(path) @pyqtSlot(bool) def load_icons_set(self, use_light_icons: bool = False) -> None: """Load a given icons set (either the default one "dark", or the light one).""" if self.use_light_icons is use_light_icons: return suffix = ("", "_light")[use_light_icons] mask = str(find_icon("active.svg")) # Icon mask for macOS for state in { "conflict", "disabled", "error", "idle", "notification", "paused", "syncing", "update", }: icon = QIcon() icon.addFile(str(find_icon(f"{state}{suffix}.svg"))) if MAC: icon.addFile(mask, mode=QIcon.Selected) self.icons[state] = icon self.use_light_icons = use_light_icons self.manager.set_config("light_icons", use_light_icons) # Reload the current showed icon if self.icon_state: self.set_icon_state(self.icon_state, force=True) def initial_icons_set(self) -> bool: """ Try to guess the most appropriate icons set at start. The user will still have the possibility to change that in Settings. """ use_light_icons = self.manager.get_config("light_icons", default=None) if use_light_icons is None: # Default value for GNU/Linux, macOS ans Windows 7 use_light_icons = False if WINDOWS: win_ver = sys.getwindowsversion() version = (win_ver.major, win_ver.minor) if version > (6, 1): # Windows 7 # Windows 8+ has a dark them by default use_light_icons = True else: # The value stored in DTB as a string '0' or '1', convert to boolean use_light_icons = bool(int(use_light_icons)) return use_light_icons def setup_systray(self) -> None: """Setup the icon system tray and its associated menu.""" self.load_icons_set(use_light_icons=self.initial_icons_set()) self.tray_icon = DriveSystrayIcon(self) if not self.tray_icon.isSystemTrayAvailable(): log.critical("There is no system tray available!") else: self.tray_icon.setToolTip(APP_NAME) self.set_icon_state("disabled") self.tray_icon.show() def _handle_language_change(self) -> None: self.manager.set_config("locale", Translator.locale()) if not MAC: self.tray_icon.setContextMenu(self.tray_icon.get_context_menu()) self.osi.register_contextual_menu() def event(self, event: QEvent) -> bool: """ Handle URL scheme events under macOS. """ url = getattr(event, "url", None) if not url: # This is not an event for us! return super().event(event) final_url = unquote(event.url().toString()) try: return self._handle_nxdrive_url(final_url) except: log.exception(f"Error handling URL event {final_url!r}") return False def _show_msgbox_restart_needed(self) -> None: msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(Translator.get("RESTART_NEEDED_MSG", values=[APP_NAME])) msg.setWindowTitle(APP_NAME) msg.addButton(Translator.get("OK"), QMessageBox.AcceptRole) msg.exec_() def _handle_nxdrive_url(self, url: str) -> bool: """ Handle an nxdrive protocol URL. """ info = parse_protocol_url(url) if not info: return False cmd = info["command"] path = normalized_path(info.get("filepath", "")) manager = self.manager log.info(f"Event URL={url}, info={info!r}") # Event fired by a context menu item func = { "access-online": manager.ctx_access_online, "copy-share-link": manager.ctx_copy_share_link, "edit-metadata": manager.ctx_edit_metadata, }.get(cmd, None) if func: func(path) elif "edit" in cmd: if self.manager.restart_needed: self._show_msgbox_restart_needed() return False manager.direct_edit.edit( info["server_url"], info["doc_id"], user=info["user"], download_url=info["download_url"], ) elif cmd == "token": self.api.handle_token(info["token"], info["username"]) else: log.warning(f"Unknown event URL={url}, info={info!r}") return False return True def init_nxdrive_listener(self) -> None: """ Set up a QLocalServer to listen to nxdrive protocol calls. On Windows, when an nxdrive:// URL is opened, it creates a new instance of Nuxeo Drive. As we want the already running instance to receive this call (particularly during the login process), we set up a QLocalServer in that instance to listen to the new ones who will send their data. The Qt implementation of QLocalSocket on Windows makes use of named pipes. We just need to connect a handler to the newConnection signal to process the URLs. """ named_pipe = f"{BUNDLE_IDENTIFIER}.protocol.{os.getpid()}" server = QLocalServer() server.setSocketOptions(QLocalServer.WorldAccessOption) server.newConnection.connect(self._handle_connection) try: server.listen(named_pipe) log.info(f"Listening for nxdrive:// calls on {server.fullServerName()}") except: log.info( f"Unable to start local server on {named_pipe}: {server.errorString()}" ) self._nxdrive_listener = server self.aboutToQuit.connect(self._nxdrive_listener.close) def _handle_connection(self) -> None: """ Retrieve the connection with other instances and handle the incoming data. """ con: QLocalSocket = None try: con = self._nxdrive_listener.nextPendingConnection() log.info("Receiving socket connection for nxdrive protocol handling") if not con or not con.waitForConnected(): log.error(f"Unable to open server socket: {con.errorString()}") return if con.waitForReadyRead(): payload = con.readAll() url = force_decode(payload.data()) self._handle_nxdrive_url(url) con.disconnectFromServer() if con.state() == QLocalSocket.ConnectedState: con.waitForDisconnected() finally: del con log.info("Successfully closed server socket") def update_status(self, engine: "Engine") -> None: """ Update the systray status for synchronization, conflicts/errors and software updates. """ sync_state = error_state = update_state = "" update_state = self.manager.updater.status self.refresh_conflicts(engine.uid) # Check synchronization state if self.manager.restart_needed: sync_state = "restart" elif engine.is_paused(): sync_state = "suspended" elif engine.is_syncing(): sync_state = "syncing" # Check error state if engine.has_invalid_credentials(): error_state = "auth_expired" elif self.conflicts_model.count: error_state = "conflicted" elif self.errors_model.count: error_state = "error" self._window_root(self.systray_window).setStatus.emit( sync_state, error_state, update_state ) @pyqtSlot() def refresh_actions(self) -> None: actions = self.api.get_actions() if actions != self.action_model.actions: self.action_model.set_actions(actions) self.action_model.fileChanged.emit() @pyqtSlot(str) def get_last_files(self, uid: str) -> None: files = self.api.get_last_files(uid, 10, "") if files != self.file_model.files: self.file_model.empty() self.file_model.addFiles(files) self.file_model.fileChanged.emit() def current_language(self) -> Optional[str]: lang = Translator.locale() for tag, name in self.language_model.languages: if tag == lang: return name return None def show_metrics_acceptance(self) -> None: """ Display a "friendly" dialog box to ask user for metrics approval. """ tr = Translator.get dialog = QDialog() dialog.setWindowTitle(tr("SHARE_METRICS_TITLE", [APP_NAME])) dialog.setWindowIcon(self.icon) dialog.setStyleSheet("background-color: #ffffff;") layout = QVBoxLayout() info = QLabel(tr("SHARE_METRICS_MSG", [COMPANY])) info.setTextFormat(Qt.RichText) info.setWordWrap(True) layout.addWidget(info) def analytics_choice(state) -> None: Options.use_analytics = bool(state) def errors_choice(state) -> None: Options.use_sentry = bool(state) # Checkboxes em_analytics = QCheckBox(tr("SHARE_METRICS_ERROR_REPORTING")) em_analytics.setChecked(True) em_analytics.stateChanged.connect(errors_choice) layout.addWidget(em_analytics) cb_analytics = QCheckBox(tr("SHARE_METRICS_ANALYTICS")) cb_analytics.stateChanged.connect(analytics_choice) layout.addWidget(cb_analytics) # Buttons buttons = QDialogButtonBox() buttons.setStandardButtons(QDialogButtonBox.Apply) buttons.clicked.connect(dialog.close) layout.addWidget(buttons) dialog.setLayout(layout) dialog.resize(400, 200) dialog.show() dialog.exec_() states = [] if Options.use_analytics: states.append("analytics") if Options.use_sentry: states.append("sentry") (Options.nxdrive_home / "metrics.state").write_text("\n".join(states)) def ask_for_metrics_approval(self) -> None: """Should we setup and use Sentry and/or Google Analytics?""" # Check the user choice first Options.nxdrive_home.mkdir(parents=True, exist_ok=True) STATE_FILE = Options.nxdrive_home / "metrics.state" if STATE_FILE.is_file(): lines = STATE_FILE.read_text().splitlines() Options.use_sentry = "sentry" in lines Options.use_analytics = "analytics" in lines # Abort now, the user already decided to use Sentry or not return # The user did not choose yet, display a message box self.show_metrics_acceptance()
# # win = engine.rootObjects()[0] # win.show() # sys.exit(app.exec_()) if __name__ == '__main__': _file = r"choice_cnx.csv" _qml = r'main.qml' # Prints QML errors def handleStatusChange(status): if status == QQuickView.Error: errors = view.errors() if errors: print(errors[0].description()) myApp = QApplication(sys.argv) qmlRegisterType(TestModel, 'TestModel', 1, 0, 'TestModel') view = QQuickView() view.rootContext().setContextProperty('fromPython', "text de la nasa") view.statusChanged.connect(handleStatusChange) view.setSource(QUrl(_qml)) try: sys.exit(myApp.exec_()) except: print("Exiting")
self.cur_alt = int(self.abs_alt) + int_alt self.newAlt.emit(self.cur_alt) def signal_new_batt_stat(self): print("new batt_stat is: " + str(self.batt_stat)) pct = self.batt_stat / 15.0 * 100 pct_str = "{0:.2f}".format(pct) rem_time = pct / 100 * 5 time_str = "{0:.2f}".format(rem_time) self.newBattStat.emit(pct_str, time_str) def signal_state_changed(self, state): print("state changed to: " + state + "\n") self.stateChanged.emit(state) app = QApplication([]) view = QQuickView() view.setWidth(1200) view.setHeight(720) view.setTitle('Telemetry Tracker') view.setResizeMode(QQuickView.SizeRootObjectToView) url = QUrl('interface.qml') gui = Gui(sys.argv[1]) gui.connect_signals() view.rootContext().setContextProperty('gui', gui) view.setSource(url) view.show() qml_window = view.rootObject() app.exec_()
def loadMainView(self): view = QQuickView() view.rootContext().setContextProperty("base", self) view.setSource(QUrl("main.qml")) self.mainWindow = self r = view.rootObject()
def main(): try: app = QGuiApplication(sys.argv) app.setApplicationName("斗鱼客户端发版") app.setWindowIcon(QIcon("./image/app_icon.ico")) app.processEvents() view = QQuickView() context = view.rootContext() ############ object define ######### ###LOG log = LOG() context.setContextProperty("_log", log) current = os.getcwd() ###ConfigManager config_manager = ConfigManager(current + "\\config\\config.json", view) context.setContextProperty("_config_manager", config_manager) ###TemplateParser template_parser = TemplateParser(current + "\\config\\template.json", view) context.setContextProperty("_template_parser", template_parser) ###VersionManager version_manager = VersionManager(current + "\\config\\version.json", view) context.setContextProperty("_version_manager", version_manager) ###UpdateManager update_manager = UpdateManager(current + "\\config\\update.json", view) context.setContextProperty("_update_manager", update_manager) ###DownloadUnziper downloader = DownloadUnziper(view) context.setContextProperty("_downloader", downloader) ###IniProducer ini_producer = IniProducer(view) context.setContextProperty("_ini_producer", ini_producer) ###ReleaseUpdater release_updater = ReleaseUpdater(view) context.setContextProperty("_release_updater", release_updater) ###InstallPackager install_packager = InstallPackager(view) context.setContextProperty("_install_packager", install_packager) #UpdateRater update_rater = UpdateRater(view) context.setContextProperty("_update_rater", update_rater) ############ signals connect slots ######### config_manager.print.connect(log.print) config_manager.finalPathChanged.connect(release_updater.onFinalPath) template_parser.print.connect(log.print) template_parser.updateChanged.connect(ini_producer.onUpdateChanged) template_parser.versionAbstractCountChanged.connect( version_manager.onVersionAbstractCount) template_parser.versionAbstractChanged.connect( ini_producer.onEnableVersionAbstract) template_parser.updateInfoChanged.connect( ini_producer.onUpdateInfoPrefix) template_parser.deletedSuffixCascadeChanged.connect( release_updater.onDeletedSuffixAscade) template_parser.deletedSuffixChanged.connect( release_updater.onDeletedSuffix) template_parser.deletedFileCascadeChanged.connect( release_updater.onDeletedFileAscade) template_parser.deletedFileChanged.connect( release_updater.onDeletedFile) template_parser.deletedFolderChanged.connect( release_updater.onDeletedFolder) template_parser.deletedWorkplaceSuffixChanged.connect( release_updater.onDeletedWorkplaceSuffix) template_parser.deletedWorkplaceFileChanged.connect( release_updater.onDeletedWorkplaceFile) template_parser.srcPathChanged.connect(release_updater.onSrcPath) template_parser.destPathChanged.connect(release_updater.onDestPath) template_parser.neededFilesChanged.connect( release_updater.onNeededFiles) template_parser.filePackageChanged.connect( release_updater.onFilePackage) template_parser.resetPrgConfigChanged.connect( release_updater.onResetPrgConfig) template_parser.peSignedChanged.connect( release_updater.onPeSignedChanged) template_parser.deletedSuffixCascadeChanged.connect( install_packager.onDeletedSuffixAscade) template_parser.deletedSuffixChanged.connect( install_packager.onDeletedSuffix) template_parser.deletedFileCascadeChanged.connect( install_packager.onDeletedFileAscade) template_parser.deletedPackageFileChanged.connect( install_packager.onDeletedFile) template_parser.deletedFolderChanged.connect( install_packager.onDeletedFolder) template_parser.peSignedChanged.connect( install_packager.onPeSignedChanged) downloader.print.connect(log.print) release_updater.print.connect(log.print, type=Qt.QueuedConnection) release_updater.versionChanged.connect( config_manager.onLastVersionChanged) release_updater.minHideVersionChanged.connect( config_manager.onMinHideVersionChanged) release_updater.addItem.connect(update_manager.onAddItem) version_manager.print.connect(log.print) version_manager.lastNVersionAbstract.connect( ini_producer.onLastNVersionAbstract) ini_producer.print.connect(log.print) ini_producer.addItem.connect(version_manager.addItem) install_packager.print.connect(log.print) update_manager.print.connect(log.print) update_manager.dataChanged.connect(update_rater.onUpdateVersionData) update_rater.print.connect(log.print) context.setContextProperty("_main_window", view) view.setSource(QUrl('./qml/main.qml')) app.aboutToQuit.connect(app.quit) root = view.rootObject() root.finalPathChanged.connect(config_manager.onFinalPath) root.finalPathChanged.connect(release_updater.onFinalPath) view.show() ret = template_parser.Parse() #解析config.json ret = config_manager.Parse() #解析version.json ret = version_manager.Parse() #解析update.json ret = update_manager.Parse() #update_rater.OpenExcel("D:\\learn\\release\\PC客户端启动版本更新率数据.xlsx") app.exec_() except Exception as e: print("main except: " + str(e))
parents.append( parents[-1].child(parents[-1].childCount() - 1)) indentations.append(position) else: while position < indentations[-1] and len(parents) > 0: parents.pop() indentations.pop() # Append a new item to the current parent's list of children. parents[-1].appendChild(TreeItem(columnData, parents[-1])) number += 1 if __name__ == '__main__': import sys app = QGuiApplication(sys.argv) view = QQuickView() f = QFile(':/default.txt') f.open(QIODevice.ReadOnly) model = TreeModel(f.readAll()) f.close() root_context = view.rootContext().setContextProperty('model', model) view.setSource(QUrl.fromLocalFile('simpletreemodel.qml')) view.show() sys.exit(app.exec_())
class P2CQMLApplication(QGuiApplication): def __init__(self, list_of_str): super().__init__(list_of_str) self._current_category = None self._current_torrent = None self._current_torrent_info = None self._status_timer = QTimer(self) self._movies_thread = None self._search_thread = None def run_view(self): self._view = QQuickView() self._view.engine().addImportPath("qml") self._rctx = self._view.rootContext() self._view.setResizeMode(QQuickView.SizeRootObjectToView) # set context variables self.categories = [] self._rctx.setContextProperty("categoriesModel", self.categories) self.tiles = [] self.torrents = [] self._rctx.setContextProperty("moviesModel", self.tiles) self._set_loading(False) self._view.setSource(QUrl('qrc:/qml.qml')) self._view.showFullScreen() # self._view.show() def connect_daemon(self, daemon: P2CDaemon): self._daemon = daemon self._set_categories() self._connect_signals() def play(self, movie: Movie): self._set_movie_status("Ready to play!") self._set_media(movie) self._daemon.play(movie) self._set_additional_media_info() def buffer(self, movie: Movie): seconds = self._current_torrent.get_seconds_to_buffer() info = "just started" if seconds: if seconds < 15: info = "just a moment" else: # rount to minutes minutes = int(seconds / 60) + 1 if minutes == 1: info = "1 minute" else: info = "{} minutes".format(minutes) self._set_movie_status("Buffering... ({})".format(info)) self._daemon.buffer(movie) self._set_additional_media_info() def wait_for_metadata(self): self._set_movie_status("Getting metadata...") if self._current_torrent: self._set_additional_media_info(self._current_torrent.name) def select_movie(self, torrent: Torrent) -> Movie: movies = torrent.get_movies() if len(movies) == 0: return # TODO: show dialog with movie selecting instead of doing it automatically return max(movies, key=lambda x: x.size) def update_status(self): torrent = self._current_torrent if torrent: if(torrent.has_torrent_info()): movie = self.select_movie(torrent) if not movie: self._set_movie_status("No movie in this torrent. Please, select another.") return torrent.download_file(movie.path) self._set_duration(movie) if not self._daemon.is_playing(movie): if(movie.can_play()): # print(movie.get_subtitles()) self.play(movie) else: self.buffer(movie) ### DEBUG INFO text = "s: %s, num p: %s, rate: %s kbs, p_rate: %s kbs" % ( torrent.get_status()['state'], torrent.get_status()['num_peers'], int(torrent.get_status()['download_rate'] / 1024), int(torrent.get_status()['download_payload_rate'] / 1024), ) self._view.rootObject().setProperty("debugText", text) ### END DEBUG INFO else: self.wait_for_metadata() else: self.wait_for_metadata() def on_category_clicked(self, index): # clear list self._set_torrents([], loading=True) category = self._daemon.get_categories()[index] self._current_category = category if self._current_category: self._search_thread = None self._movies_thread = SetMoviesThread(self._current_category) self._movies_thread.start() self._movies_thread.got_movies.connect(self._threaded_set_torrents) def on_movie_clicked(self, index): self._view.rootObject().setProperty("isMovieScene", True) torrent_ui = self.torrents[index] self._current_torrent = self._daemon.get_torrent(torrent_ui) self._current_torrent_info = torrent_ui self.update_status() def on_search(self, query): if len(query) < 3: return # clear list self._set_torrents([], loading=True) self._movies_thread = None self._search_thread = SearchThread(query, self._daemon.search) self._search_thread.start() self._search_thread.got_movies.connect(self._threaded_set_torrents) def on_exit(self): self.quit() def _connect_signals(self): self._view.rootObject().categoryClicked.connect( self.on_category_clicked) self._view.rootObject().movieClicked.connect(self.on_movie_clicked) self._view.rootObject().movieClicked.connect(self.on_movie_clicked) self._view.rootObject().searchQuery.connect(self.on_search) self._view.rootObject().exitAction.connect(self.on_exit) self._status_timer.timeout.connect(self.update_status) self._status_timer.start(500) def _set_movie_status(self, text): self._rctx.setContextProperty("movieStatus", text) def _set_media(self, movie: Movie): file_name = movie.get_target_path() self._rctx.setContextProperty("movieSource", QUrl.fromLocalFile(file_name)) def _set_additional_media_info(self, title=None): self._rctx.setContextProperty("title", title or self._current_torrent_info.title or self._current_torrent_info.label) self._rctx.setContextProperty("poster", self._current_torrent_info.poster if self._current_torrent_info and self._current_torrent_info.poster else '') def _set_categories(self): data = [] for category in self._daemon.get_categories(): data.append(Tile(category.label, category.service.name)) self._rctx.setContextProperty("categoriesModel", data) self.categories = data def _threaded_set_torrents(self, data, thread): # if latest action if thread == self._movies_thread or thread == self._search_thread: self._set_torrents(data) def _set_torrents(self, data, loading=False): # only existing tiles for (tile, torrent_info) in zip(self.tiles, data[:len(self.tiles)]): if torrent_info.title: tile.name = torrent_info.title tile.source = torrent_info.label else: tile.name = torrent_info.label tile.source = None tile.poster = torrent_info.poster tile.description = torrent_info.description if len(data) != len(self.tiles): for torrent_info in data[len(self.tiles):]: if torrent_info.title: tile = Tile(torrent_info.title, torrent_info.label, torrent_info.poster, torrent_info.description) else: tile = Tile(torrent_info.label, None, torrent_info.poster, torrent_info.description) self.tiles.append(tile) self._rctx.setContextProperty("moviesModel", self.tiles) self.torrents = data self._set_loading(loading) def _set_loading(self, loading): self._rctx.setContextProperty("loadingMask", loading) def _set_duration(self, movie:Movie): tdelta = movie.get_movie_duration() if tdelta: self._view.rootObject().setProperty("movieDuration", tdelta.seconds * 1000)
try: from dbus.mainloop.glib import DBusGMainLoop dbus_loop = DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() session = bus.get_object('org.qoverview.config', '/org/qoverview/config') config = dbus.Interface(session, 'org.qoverview.config.iface') except dbus.DBusException as e: print(e) print('Unable to connect to config-server via DBUS') sys.exit(1) print('KDE Frameworks:', 'Yes' if KDE_FRAMEWORKS else 'No') qmlview = QQuickView(QUrl('/usr/lib/qoverview/ui.qml')) qmlview.setResizeMode(qmlview.SizeRootObjectToView) root = qmlview.rootObject() context = qmlview.rootContext() interface = PythonQMLInterface(view=qmlview, uid=uid, app=app) context.setContextProperty('Python', interface) qmlview.setTitle(interface.uid) print(interface.uid) qmlview.showFullScreen() app.exec_()
class P2CQMLApplication(QGuiApplication): def __init__(self, list_of_str): super().__init__(list_of_str) self._current_category = None self._current_torrent = None self._current_torrent_info = None self._status_timer = QTimer(self) self._movies_thread = None self._search_thread = None def run_view(self): self._view = QQuickView() self._view.engine().addImportPath("qml") self._rctx = self._view.rootContext() self._view.setResizeMode(QQuickView.SizeRootObjectToView) # set context variables self.categories = [] self._rctx.setContextProperty("categoriesModel", self.categories) self.tiles = [] self.torrents = [] self._rctx.setContextProperty("moviesModel", self.tiles) self._set_loading(False) self._view.setSource(QUrl('qrc:/qml.qml')) self._view.showFullScreen() # self._view.show() def connect_daemon(self, daemon: P2CDaemon): self._daemon = daemon self._set_categories() self._connect_signals() def play(self, movie: Movie): self._set_movie_status("Ready to play!") self._set_media(movie) self._daemon.play(movie) self._set_additional_media_info() def buffer(self, movie: Movie): seconds = self._current_torrent.get_seconds_to_buffer() info = "just started" if seconds: if seconds < 15: info = "just a moment" else: # rount to minutes minutes = int(seconds / 60) + 1 if minutes == 1: info = "1 minute" else: info = "{} minutes".format(minutes) self._set_movie_status("Buffering... ({})".format(info)) self._daemon.buffer(movie) self._set_additional_media_info() def wait_for_metadata(self): self._set_movie_status("Getting metadata...") if self._current_torrent: self._set_additional_media_info(self._current_torrent.name) def select_movie(self, torrent: Torrent) -> Movie: movies = torrent.get_movies() if len(movies) == 0: return # TODO: show dialog with movie selecting instead of doing it automatically return max(movies, key=lambda x: x.size) def update_status(self): torrent = self._current_torrent if torrent: if (torrent.has_torrent_info()): movie = self.select_movie(torrent) if not movie: self._set_movie_status( "No movie in this torrent. Please, select another.") return torrent.download_file(movie.path) self._set_duration(movie) if not self._daemon.is_playing(movie): if (movie.can_play()): # print(movie.get_subtitles()) self.play(movie) else: self.buffer(movie) ### DEBUG INFO text = "s: %s, num p: %s, rate: %s kbs, p_rate: %s kbs" % ( torrent.get_status()['state'], torrent.get_status()['num_peers'], int(torrent.get_status()['download_rate'] / 1024), int(torrent.get_status()['download_payload_rate'] / 1024), ) self._view.rootObject().setProperty("debugText", text) ### END DEBUG INFO else: self.wait_for_metadata() else: self.wait_for_metadata() def on_category_clicked(self, index): # clear list self._set_torrents([], loading=True) category = self._daemon.get_categories()[index] self._current_category = category if self._current_category: self._search_thread = None self._movies_thread = SetMoviesThread(self._current_category) self._movies_thread.start() self._movies_thread.got_movies.connect(self._threaded_set_torrents) def on_movie_clicked(self, index): self._view.rootObject().setProperty("isMovieScene", True) torrent_ui = self.torrents[index] self._current_torrent = self._daemon.get_torrent(torrent_ui) self._current_torrent_info = torrent_ui self.update_status() def on_search(self, query): if len(query) < 3: return # clear list self._set_torrents([], loading=True) self._movies_thread = None self._search_thread = SearchThread(query, self._daemon.search) self._search_thread.start() self._search_thread.got_movies.connect(self._threaded_set_torrents) def on_exit(self): self.quit() def _connect_signals(self): self._view.rootObject().categoryClicked.connect( self.on_category_clicked) self._view.rootObject().movieClicked.connect(self.on_movie_clicked) self._view.rootObject().movieClicked.connect(self.on_movie_clicked) self._view.rootObject().searchQuery.connect(self.on_search) self._view.rootObject().exitAction.connect(self.on_exit) self._status_timer.timeout.connect(self.update_status) self._status_timer.start(500) def _set_movie_status(self, text): self._rctx.setContextProperty("movieStatus", text) def _set_media(self, movie: Movie): file_name = movie.get_target_path() self._rctx.setContextProperty("movieSource", QUrl.fromLocalFile(file_name)) def _set_additional_media_info(self, title=None): self._rctx.setContextProperty( "title", title or self._current_torrent_info.title or self._current_torrent_info.label) self._rctx.setContextProperty( "poster", self._current_torrent_info.poster if self._current_torrent_info and self._current_torrent_info.poster else '') def _set_categories(self): data = [] for category in self._daemon.get_categories(): data.append(Tile(category.label, category.service.name)) self._rctx.setContextProperty("categoriesModel", data) self.categories = data def _threaded_set_torrents(self, data, thread): # if latest action if thread == self._movies_thread or thread == self._search_thread: self._set_torrents(data) def _set_torrents(self, data, loading=False): # only existing tiles for (tile, torrent_info) in zip(self.tiles, data[:len(self.tiles)]): if torrent_info.title: tile.name = torrent_info.title tile.source = torrent_info.label else: tile.name = torrent_info.label tile.source = None tile.poster = torrent_info.poster tile.description = torrent_info.description if len(data) != len(self.tiles): for torrent_info in data[len(self.tiles):]: if torrent_info.title: tile = Tile(torrent_info.title, torrent_info.label, torrent_info.poster, torrent_info.description) else: tile = Tile(torrent_info.label, None, torrent_info.poster, torrent_info.description) self.tiles.append(tile) self._rctx.setContextProperty("moviesModel", self.tiles) self.torrents = data self._set_loading(loading) def _set_loading(self, loading): self._rctx.setContextProperty("loadingMask", loading) def _set_duration(self, movie: Movie): tdelta = movie.get_movie_duration() if tdelta: self._view.rootObject().setProperty("movieDuration", tdelta.seconds * 1000)
if __name__ == "__main__": global hector hector = Hector(config) readChannelConfiguration() os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material" app = QGuiApplication(sys.argv) global view view = QQuickView() view.setResizeMode(QQuickView.SizeRootObjectToView) global screenMgr screenMgr = ScreenManager() view.rootContext().setContextProperty("screenManager", screenMgr) menuModel = MenuModel() for idx, drink in enumerate(available_drinks): menuModel.items.append({ 'menuId': '%d' % idx, 'name': drink["name"] + ("\n\n‰" if alcoholic(drink) else ""), 'colorCode': drink["color"]}) view.rootContext().setContextProperty("menuModel", menuModel) menuMgr = MenuManager() menuMgr.setAction(drinkRequested) view.rootContext().setContextProperty("menuManager", menuMgr) dispensingModel = DispensingModel(12) for idx, ingr in enumerate(channelList):
subscription = lc.subscribe("SIMULATOR", my_handler) class WorkThread(QThread): # 定义一个信号 trigger = pyqtSignal(str) def __int__(self): # 初始化函数,默认 super(WorkThread, self).__init__() def run(self): # 等待5秒后,给触发信号,并传递test # time.sleep(2) self.trigger.emit("ok") HandleWork.lc.handle() if __name__ == "__main__": path = 'main.qml' app = QGuiApplication([]) viewer = QQuickView() con = MainPanel() context = viewer.rootContext() context.setContextProperty("con", con) viewer.engine().quit.connect(app.quit) viewer.setSource(QUrl(path)) rootObject = viewer.rootObject() viewer.show() app.exec_()
class PhotoBoothGUI(QObject): def run(self): # Create main app self.myApp = QApplication(sys.argv) # Create a label and set its properties self.appLabel = QQuickView() #self.appLabel.setSource(QUrl('loading.qml')) # Show the Label self.appLabel.show() # Initialize PhotoBoothEngine. self.pbengine = PhotoBoothEngine() self.pbengine.on_change_url.connect(self.update_url_signal) self.pbengine.on_connect_signal.connect(self.connect_signal) self.pbengine.change_qml(0) self.pbengine.connect_state(0) print("UPDATE") #self.pbengine.on_status.connect(self.appLabel.rootObject().status) #self.pbengine.on_update_filter_preview.connect(self.appLabel.rootObject().updateImageFilterPreview) self.appLabel.rootContext().setContextProperty('pbengine', self.pbengine) self.setup_text_status_fly_component() self.pbengine.start_state_thread(0) # Execute the Application and Exit self.myApp.exec_() sys.exit() def setup_text_status_fly_component(self): # Create a component factory and load the QML script. print("Hello") self.component = QQmlComponent(self.appLabel.engine()) self.component.loadUrl(QUrl('qml/TextStatusFly.qml')) print("Hello2") self.statuswidget = self.component.create(self.appLabel.rootContext()) print("Hello3") self.statuswidget.setParentItem(self.appLabel.rootObject()) self.statuswidget.setParent(self.appLabel.rootObject()) print("Hello4") #statuswidget.setProperty("targetX", 100) self.statuswidget.setProperty("objectName", "textStatusBar") print("Hello5") self.appLabel.rootContext().setContextProperty('textStatusBar', self.statuswidget) self.statuswidget.setProperty("parentSet", True) def update_url_signal(self, url): print(" ** Updating URL: %s" % url) #self.pbengine.on_change_url.disconnect() #self.pbengine.on_connect_signal.disconnect() self.appLabel.rootContext().setContextProperty('textStatusBar', None) self.appLabel.setSource(QUrl()) self.appLabel.engine().clearComponentCache() self.appLabel.setSource(QUrl(url)) self.setup_text_status_fly_component() self.appLabel.show() # Reconnect #self.pbengine.on_change_url.connect(self.update_url_signal) #self.pbengine.on_connect_signal.connect(self.connect_signal) def connect_signal(self, signal, target): print(" ** Binding signal %s to target %s!" % (str(signal), target)) print(" ** (getattr(self.appLabel, target) = %s)" % (str(getattr(self.appLabel.rootObject(), target)))) signal.connect(getattr(self.appLabel.rootObject(), target))
from PyQt5.QtGui import QGuiApplication if __name__ == '__main__': #get our data url = "http://country.io/names.json" response = urllib.request.urlopen(url) data = json.loads(response.read().decode('utf-8')) #Format and sort the data data_list = list(data.values()) data_list.sort() #Set up the application window app = QGuiApplication(sys.argv) view = QQuickView() view.setResizeMode(QQuickView.SizeRootObjectToView) #Expose the list to the Qml code my_model = QStringListModel() my_model.setStringList(data_list) view.rootContext().setContextProperty("myModel", my_model) #Load the QML file qml_file = os.path.join(os.path.dirname(__file__), "test.ui.qml") view.setSource(QUrl.fromLocalFile(os.path.abspath(qml_file))) #Show the window if view.status() == QQuickView.Error: sys.exit(-1) view.show()
def sniff_data(qroot): cap = pyshark.FileCapture('D:\\4.pcapng') for p in cap: if 'MODBUS' in p: if p['IP'].src == '192.168.0.103': packet_windturbine_1(p, qroot) else: continue if __name__ == '__main__': path = 'main.qml' app = QGuiApplication([]) view = QQuickView() view.engine().quit.connect(app.quit) view.setSource(QUrl(path)) view.show() context = view.rootContext() rootobj = view.rootObject() sniff_thread = Thread(target=sniff_data, args=(rootobj,)) sniff_thread.setDaemon(True) sniff_thread.start() app.exec_()
qmlRegisterType(DataBlock, 'WeatherCategory', 1, 0, 'DataBlock') qmlRegisterType(Geocoder, 'WeatherCategory', 1, 0, 'Geocoder') component = QQmlComponent(engine) component.loadUrl(QUrl('WeatherDash.qml')) # Create the QML user interface. Auto creates its own engine view = QQuickView() engine2 = view.engine # Does not run #engine2.quit.connect(app.quit) #view.setSource(QUrl('PyTest.qml')) # To Satisfy cool-retro-term needs view.rootContext().setContextProperty('devicePixelRatio', app.devicePixelRatio()) view.setSource(QUrl('WeatherDash.qml')) #view.setResizeMode(QDeclarativeView.SizeRootObjectToView) view.setGeometry(100, 100, 750, 480) # ala https://pythonspot.com/pyqt5-colors/ view.setColor(QColor(0, 30, 0)) view.show() is_full_screen = False # technique lifted from https://stackoverflow.com/questions/19131084/pyqt5-qml-signal-to-python-slot # and augmented from https://stackoverflow.com/questions/30586983/how-to-close-pyqt5-program-from-qml # could refine with https://stackoverflow.com/questions/24111717/how-to-bind-buttons-in-qt-quick-to-python-pyqt-5 # not 100% ideal, but adequate and interesting
class Application(QApplication): """Main Nuxeo Drive application controlled by a system tray icon + menu""" tray_icon = None icon_state = None def __init__(self, manager: "Manager", *args: Any) -> None: super().__init__(list(*args)) self.manager = manager self.dialogs = dict() self.osi = self.manager.osi self.setWindowIcon(QIcon(self.get_window_icon())) self.setApplicationName(manager.app_name) self._init_translator() self.setQuitOnLastWindowClosed(False) self._delegator = None self.filters_dlg = None self._conflicts_modals = dict() self.current_notification = None self.default_tooltip = self.manager.app_name font = QFont("Helvetica, Arial, sans-serif", 12) self.setFont(font) self.ratio = sqrt(QFontMetricsF(font).height() / 12) self.init_gui() self.aboutToQuit.connect(self.manager.stop) self.manager.dropEngine.connect(self.dropped_engine) # This is a windowless application mostly using the system tray self.setQuitOnLastWindowClosed(False) self.setup_systray() # Direct Edit self.manager.direct_edit.directEditConflict.connect( self._direct_edit_conflict) self.manager.direct_edit.directEditError.connect( self._direct_edit_error) # Check if actions is required, separate method so it can be overridden self.init_checks() # Setup notification center for macOS if MAC: self._setup_notification_center() # Application update self.manager.updater.appUpdated.connect(self.quit) # Display release notes on new version if self.manager.old_version != self.manager.version: self.show_release_notes(self.manager.version) # Listen for nxdrive:// sent by a new instance self.init_nxdrive_listener() def init_gui(self) -> None: self.api = QMLDriveApi(self) self.conflicts_model = FileModel() self.errors_model = FileModel() self.engine_model = EngineModel() self.file_model = FileModel() self.ignoreds_model = FileModel() self.language_model = LanguageModel() self.add_engines(list(self.manager._engines.values())) self.engine_model.statusChanged.connect(self.update_status) self.language_model.addLanguages(Translator.languages()) flags = Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint if WINDOWS: self.conflicts_window = QQuickView() self.settings_window = QQuickView() self.systray_window = SystrayWindow() self._fill_qml_context(self.conflicts_window.rootContext()) self._fill_qml_context(self.settings_window.rootContext()) self._fill_qml_context(self.systray_window.rootContext()) self.systray_window.rootContext().setContextProperty( "systrayWindow", self.systray_window) self.conflicts_window.setSource( QUrl.fromLocalFile(find_resource("qml", "Conflicts.qml"))) self.settings_window.setSource( QUrl.fromLocalFile(find_resource("qml", "Settings.qml"))) self.systray_window.setSource( QUrl.fromLocalFile(find_resource("qml", "Systray.qml"))) flags |= Qt.Popup else: self.app_engine = QQmlApplicationEngine() self._fill_qml_context(self.app_engine.rootContext()) self.app_engine.load( QUrl.fromLocalFile(find_resource("qml", "Main.qml"))) root = self.app_engine.rootObjects()[0] self.conflicts_window = root.findChild(QQuickWindow, "conflictsWindow") self.settings_window = root.findChild(QQuickWindow, "settingsWindow") self.systray_window = root.findChild(SystrayWindow, "systrayWindow") if LINUX: flags |= Qt.Drawer self.systray_window.setFlags(flags) self.manager.newEngine.connect(self.add_engines) self.manager.initEngine.connect(self.add_engines) self.manager.dropEngine.connect(self.remove_engine) self._window_root(self.conflicts_window).changed.connect( self.refresh_conflicts) self._window_root(self.systray_window).appUpdate.connect( self.api.app_update) self._window_root(self.systray_window).getLastFiles.connect( self.get_last_files) self.api.setMessage.connect( self._window_root(self.settings_window).setMessage) if self.manager.get_engines(): current_uid = self.engine_model.engines_uid[0] self.get_last_files(current_uid) self.update_status(self.engine_model.engines[current_uid]) self.manager.updater.updateAvailable.connect( self._window_root(self.systray_window).updateAvailable) self.manager.updater.updateProgress.connect( self._window_root(self.systray_window).updateProgress) def add_engines(self, engines: Union["Engine", List["Engine"]]) -> None: if not engines: return engines = engines if isinstance(engines, list) else [engines] for engine in engines: self.engine_model.addEngine(engine) def remove_engine(self, uid: str) -> None: self.engine_model.removeEngine(uid) def _fill_qml_context(self, context: "QQmlContext") -> None: """ Fill the context of a QML element with the necessary resources. """ context.setContextProperty("ConflictsModel", self.conflicts_model) context.setContextProperty("ErrorsModel", self.errors_model) context.setContextProperty("EngineModel", self.engine_model) context.setContextProperty("FileModel", self.file_model) context.setContextProperty("IgnoredsModel", self.ignoreds_model) context.setContextProperty("languageModel", self.language_model) context.setContextProperty("api", self.api) context.setContextProperty("application", self) context.setContextProperty("currentLanguage", self.current_language()) context.setContextProperty("manager", self.manager) context.setContextProperty("updater", self.manager.updater) context.setContextProperty("ratio", self.ratio) context.setContextProperty("isFrozen", Options.is_frozen) context.setContextProperty("tl", Translator._singleton) context.setContextProperty( "nuxeoVersionText", f"{self.manager.app_name} {self.manager.version}") metrics = self.manager.get_metrics() context.setContextProperty( "modulesVersionText", (f'Python {metrics["python_version"]}, ' f'Qt {metrics["qt_version"]}, ' f'SIP {metrics["sip_version"]}'), ) colors = { "darkBlue": "#1F28BF", "nuxeoBlue": "#0066FF", "lightBlue": "#00ADED", "teal": "#73D2CF", "purple": "#8400FF", "red": "#C02828", "orange": "#FF9E00", "darkGray": "#495055", "mediumGray": "#7F8284", "lightGray": "#BCBFBF", "lighterGray": "#F5F5F5", } for name, value in colors.items(): context.setContextProperty(name, value) def _window_root(self, window): if WINDOWS: return window.rootObject() return window def translate(self, message: str, values: dict = None) -> str: return Translator.get(message, values) def _show_window(self, window: "QWindow") -> None: window.show() window.raise_() def _destroy_dialog(self) -> None: sender = self.sender() name = sender.objectName() self.dialogs.pop(name, None) def _create_unique_dialog(self, name: str, dialog: QDialog) -> None: dialog.setObjectName(name) dialog.destroyed.connect(self._destroy_dialog) self.dialogs[name] = dialog def _init_translator(self) -> None: locale = Options.force_locale or Options.locale Translator( self.manager, find_resource("i18n"), self.manager.get_config("locale", locale), ) self.installTranslator(Translator._singleton) @staticmethod def get_window_icon() -> str: return find_icon("app_icon.svg") @pyqtSlot(str, str, str) def _direct_edit_conflict(self, filename: str, ref: str, digest: str) -> None: log.trace("Entering _direct_edit_conflict for %r / %r", filename, ref) try: if filename in self._conflicts_modals: log.trace("Filename already in _conflicts_modals: %r", filename) return log.trace("Putting filename in _conflicts_modals: %r", filename) self._conflicts_modals[filename] = True msg = QMessageBox() msg.setInformativeText( Translator.get("DIRECT_EDIT_CONFLICT_MESSAGE", [short_name(filename)])) overwrite = msg.addButton( Translator.get("DIRECT_EDIT_CONFLICT_OVERWRITE"), QMessageBox.AcceptRole) msg.addButton(Translator.get("CANCEL"), QMessageBox.RejectRole) msg.setIcon(QMessageBox.Warning) if msg.clickedButton() == overwrite: self.manager.direct_edit.force_update(ref, digest) del self._conflicts_modals[filename] except: log.exception( "Error while displaying Direct Edit conflict modal dialog for %r", filename, ) @pyqtSlot(str, list) def _direct_edit_error(self, message: str, values: List[str]) -> None: """ Display a simple Direct Edit error message. """ msg = QMessageBox() msg.setWindowTitle("Direct Edit - {self.manager.app_name}") msg.setWindowIcon(QIcon(self.get_window_icon())) msg.setIcon(QMessageBox.Warning) msg.setTextFormat(Qt.RichText) msg.setText(self.translate(message, values)) msg.exec_() @pyqtSlot() def _root_deleted(self) -> None: engine = self.sender() log.debug("Root has been deleted for engine: %s", engine.uid) msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setWindowIcon(QIcon(self.get_window_icon())) msg.setText(Translator.get("DRIVE_ROOT_DELETED", [engine.local_folder])) recreate = msg.addButton(Translator.get("DRIVE_ROOT_RECREATE"), QMessageBox.AcceptRole) disconnect = msg.addButton(Translator.get("DRIVE_ROOT_DISCONNECT"), QMessageBox.RejectRole) msg.exec_() res = msg.clickedButton() if res == disconnect: self.manager.unbind_engine(engine.uid) elif res == recreate: engine.reinit() engine.start() @pyqtSlot() def _no_space_left(self) -> None: msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowIcon(QIcon(self.get_window_icon())) msg.setText(Translator.get("NO_SPACE_LEFT_ON_DEVICE")) msg.addButton(Translator.get("OK"), QMessageBox.AcceptRole) msg.exec_() @pyqtSlot(str) def _root_moved(self, new_path: str) -> None: engine = self.sender() log.debug("Root has been moved for engine: %s to %r", engine.uid, new_path) info = [engine.local_folder, new_path] msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setWindowIcon(QIcon(self.get_window_icon())) msg.setText(Translator.get("DRIVE_ROOT_MOVED", info)) move = msg.addButton(Translator.get("DRIVE_ROOT_UPDATE"), QMessageBox.AcceptRole) recreate = msg.addButton(Translator.get("DRIVE_ROOT_RECREATE"), QMessageBox.AcceptRole) disconnect = msg.addButton(Translator.get("DRIVE_ROOT_DISCONNECT"), QMessageBox.RejectRole) msg.exec_() res = msg.clickedButton() if res == disconnect: self.manager.unbind_engine(engine.uid) elif res == recreate: engine.reinit() engine.start() elif res == move: engine.set_local_folder(new_path) engine.start() @pyqtSlot(object) def dropped_engine(self, engine: "Engine") -> None: # Update icon in case the engine dropped was syncing self.change_systray_icon() @pyqtSlot() def change_systray_icon(self) -> None: # Update status has the precedence over other ones if self.manager.updater.status not in ( UPDATE_STATUS_UNAVAILABLE_SITE, UPDATE_STATUS_UP_TO_DATE, ): self.set_icon_state("update") return syncing = conflict = False engines = self.manager.get_engines() invalid_credentials = paused = offline = True for engine in engines.values(): syncing |= engine.is_syncing() invalid_credentials &= engine.has_invalid_credentials() paused &= engine.is_paused() offline &= engine.is_offline() conflict |= bool(engine.get_conflicts()) if offline: new_state = "error" Action(Translator.get("OFFLINE")) elif invalid_credentials: new_state = "error" Action(Translator.get("AUTH_EXPIRED")) elif not engines: new_state = "disabled" Action.finish_action() elif paused: new_state = "paused" Action.finish_action() elif syncing: new_state = "syncing" elif conflict: new_state = "conflict" else: new_state = "idle" Action.finish_action() self.set_icon_state(new_state) def refresh_conflicts(self, uid: str) -> None: """ Update the content of the conflicts/errors window. """ self.conflicts_model.empty() self.errors_model.empty() self.ignoreds_model.empty() self.conflicts_model.addFiles(self.api.get_conflicts(uid)) self.errors_model.addFiles(self.api.get_errors(uid)) self.ignoreds_model.addFiles(self.api.get_unsynchronizeds(uid)) @pyqtSlot() def show_conflicts_resolution(self, engine: "Engine") -> None: """ Display the conflicts/errors window. """ self.refresh_conflicts(engine.uid) self._window_root(self.conflicts_window).setEngine.emit(engine.uid) self.conflicts_window.show() self.conflicts_window.requestActivate() @pyqtSlot() def show_settings(self, section: str = "General") -> None: sections = {"General": 0, "Accounts": 1, "About": 2} self._window_root(self.settings_window).setSection.emit( sections[section]) self.settings_window.show() self.settings_window.requestActivate() @pyqtSlot() def show_systray(self) -> None: icon = self.tray_icon.geometry() if not icon or icon.isEmpty(): # On Ubuntu it's likely we can't retrieve the geometry. # We're simply displaying the systray in the top right corner. screen = self.desktop().screenGeometry() pos_x = screen.right() - self.systray_window.width() - 20 pos_y = 30 else: pos_x = max(0, icon.x() + icon.width() - self.systray_window.width()) pos_y = icon.y() - self.systray_window.height() if pos_y < 0: pos_y = icon.y() + icon.height() self.systray_window.setX(pos_x) self.systray_window.setY(pos_y) self.systray_window.show() self.systray_window.raise_() @pyqtSlot() def hide_systray(self): self.systray_window.hide() @pyqtSlot() def open_help(self) -> None: self.manager.open_help() @pyqtSlot() def destroyed_filters_dialog(self) -> None: self.filters_dlg = None @pyqtSlot() def show_filters(self, engine: "Engine") -> None: if self.filters_dlg: self.filters_dlg.close() self.filters_dlg = None from ..gui.folders_dialog import FiltersDialog self.filters_dlg = FiltersDialog(self, engine) self.filters_dlg.destroyed.connect(self.destroyed_filters_dialog) self.filters_dlg.show() def show_file_status(self) -> None: from ..gui.status_dialog import StatusDialog for _, engine in self.manager.get_engines().items(): self.status = StatusDialog(engine.get_dao()) self.status.show() break def show_activities(self) -> None: return # TODO: Create activities window self.activities.show() @pyqtSlot(str, object) def _open_authentication_dialog(self, url: str, callback_params: Dict[str, str]) -> None: self.api._callback_params = callback_params if Options.is_frozen: """ Authenticate through the browser. This authentication requires the server's Nuxeo Drive addon to include NXP-25519. Instead of opening the server's login page in a WebKit view through the app, it opens in the browser and retrieves the login token by opening an nxdrive:// URL. """ self.manager.open_local_file(url) else: self._web_auth_not_frozen(url) def _web_auth_not_frozen(self, url: str) -> None: """ Open a dialog box to fill the credentials. Then a request will be done using the Python client to get a token. This is used when the application is not frozen as there is no custom protocol handler in this case. """ from PyQt5.QtWidgets import QLineEdit from nuxeo.client import Nuxeo dialog = QDialog() dialog.setWindowTitle( self.translate("WEB_AUTHENTICATION_WINDOW_TITLE")) dialog.setWindowIcon(QIcon(self.get_window_icon())) dialog.resize(250, 100) layout = QVBoxLayout() username = QLineEdit("Administrator", parent=dialog) password = QLineEdit("Administrator", parent=dialog) password.setEchoMode(QLineEdit.Password) layout.addWidget(username) layout.addWidget(password) def auth() -> None: """Retrieve a token and create the account.""" user = str(username.text()) pwd = str(password.text()) nuxeo = Nuxeo(host=url, auth=(user, pwd)) try: token = nuxeo.client.request_auth_token( device_id=self.manager.device_id, app_name=APP_NAME, permission=TOKEN_PERMISSION, device=get_device(), ) except Exception as exc: log.error(f"Connection error: {exc}") token = "" finally: del nuxeo self.api.handle_token(token, user) dialog.close() buttons = QDialogButtonBox() buttons.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) buttons.accepted.connect(auth) buttons.rejected.connect(dialog.close) layout.addWidget(buttons) dialog.setLayout(layout) dialog.exec_() @pyqtSlot(object) def _connect_engine(self, engine: "Engine") -> None: engine.syncStarted.connect(self.change_systray_icon) engine.syncCompleted.connect(self.change_systray_icon) engine.invalidAuthentication.connect(self.change_systray_icon) engine.syncSuspended.connect(self.change_systray_icon) engine.syncResumed.connect(self.change_systray_icon) engine.offline.connect(self.change_systray_icon) engine.online.connect(self.change_systray_icon) engine.rootDeleted.connect(self._root_deleted) engine.rootMoved.connect(self._root_moved) engine.noSpaceLeftOnDevice.connect(self._no_space_left) self.change_systray_icon() @pyqtSlot() def _debug_toggle_invalid_credentials(self) -> None: sender = self.sender() engine = sender.data() engine.set_invalid_credentials(not engine.has_invalid_credentials(), reason="debug") @pyqtSlot() def _debug_show_file_status(self) -> None: from ..gui.status_dialog import StatusDialog sender = self.sender() engine = sender.data() self.status_dialog = StatusDialog(engine.get_dao()) self.status_dialog.show() def _create_debug_engine_menu(self, engine: "Engine", parent: QMenu) -> QMenu: menu = QMenu(parent) action = QAction(Translator.get("DEBUG_INVALID_CREDENTIALS"), menu) action.setCheckable(True) action.setChecked(engine.has_invalid_credentials()) action.setData(engine) action.triggered.connect(self._debug_toggle_invalid_credentials) menu.addAction(action) action = QAction(Translator.get("DEBUG_FILE_STATUS"), menu) action.setData(engine) action.triggered.connect(self._debug_show_file_status) menu.addAction(action) return menu def create_debug_menu(self, menu: QMenu) -> None: menu.addAction(Translator.get("DEBUG_WINDOW"), self.show_debug_window) for engine in self.manager.get_engines().values(): action = QAction(engine.name, menu) action.setMenu(self._create_debug_engine_menu(engine, menu)) action.setData(engine) menu.addAction(action) @pyqtSlot() def show_debug_window(self) -> None: return debug = self.dialogs.get("debug") # TODO: if not debug: Create debug window self._show_window(debug) def init_checks(self) -> None: if Options.debug: self.show_debug_window() for _, engine in self.manager.get_engines().items(): self._connect_engine(engine) self.manager.newEngine.connect(self._connect_engine) self.manager.notification_service.newNotification.connect( self._new_notification) self.manager.updater.updateAvailable.connect(self._update_notification) if not self.manager.get_engines(): self.show_settings() else: for engine in self.manager.get_engines().values(): # Prompt for settings if needed if engine.has_invalid_credentials(): self.show_settings("Accounts_" + engine.uid) break self.manager.start() @pyqtSlot() @if_frozen def _update_notification(self) -> None: self.change_systray_icon() # Display a notification status, version = self.manager.updater.status, self.manager.updater.version msg = ( "AUTOUPDATE_UPGRADE", "AUTOUPDATE_DOWNGRADE")[status == UPDATE_STATUS_DOWNGRADE_NEEDED] description = Translator.get(msg, [version]) flags = Notification.FLAG_BUBBLE | Notification.FLAG_UNIQUE if LINUX: description += " " + Translator.get("AUTOUPDATE_MANUAL") flags |= Notification.FLAG_SYSTRAY log.warning(description) notification = Notification( uuid="AutoUpdate", flags=flags, title=Translator.get("NOTIF_UPDATE_TITLE", [version]), description=description, ) self.manager.notification_service.send_notification(notification) @pyqtSlot() def message_clicked(self) -> None: if self.current_notification: self.manager.notification_service.trigger_notification( self.current_notification.uid) def _setup_notification_center(self) -> None: from ..osi.darwin.pyNotificationCenter import ( setup_delegator, NotificationDelegator, ) if not self._delegator: self._delegator = NotificationDelegator.alloc().init() self._delegator._manager = self.manager setup_delegator(self._delegator) @pyqtSlot(object) def _new_notification(self, notif: Notification) -> None: if not notif.is_bubble(): return if self._delegator is not None: # Use notification center from ..osi.darwin.pyNotificationCenter import notify return notify(notif.title, None, notif.description, user_info={"uuid": notif.uid}) icon = QSystemTrayIcon.Information if notif.level == Notification.LEVEL_WARNING: icon = QSystemTrayIcon.Warning elif notif.level == Notification.LEVEL_ERROR: icon = QSystemTrayIcon.Critical self.current_notification = notif self.tray_icon.showMessage(notif.title, notif.description, icon, 10000) def set_icon_state(self, state: str) -> bool: """ Execute systray icon change operations triggered by state change. The synchronization thread can update the state info but cannot directly call QtGui widget methods. This should be executed by the main thread event loop, hence the delegation to this method that is triggered by a signal to allow for message passing between the 2 threads. Return True of the icon has changed state. """ if self.icon_state == state: # Nothing to update return False self.tray_icon.setToolTip(self.get_tooltip()) self.tray_icon.setIcon(self.icons[state]) self.icon_state = state return True def get_tooltip(self) -> str: actions = Action.get_actions() if not actions: return self.default_tooltip # Display only the first action for now for action in actions.values(): if action and action.type and not action.type.startswith("_"): break else: return self.default_tooltip if isinstance(action, FileAction): if action.get_percent() is not None: return "%s - %s - %s - %d%%" % ( self.default_tooltip, action.type, action.filename, action.get_percent(), ) return "%s - %s - %s" % (self.default_tooltip, action.type, action.filename) elif action.get_percent() is not None: return "%s - %s - %d%%" % ( self.default_tooltip, action.type, action.get_percent(), ) return "%s - %s" % (self.default_tooltip, action.type) @if_frozen def show_release_notes(self, version: str) -> None: """ Display release notes of a given version. """ beta = self.manager.get_beta_channel() log.debug("Showing release notes, version=%r beta=%r", version, beta) # For now, we do care about beta only if not beta: return url = ("https://api.github.com/repos/nuxeo/nuxeo-drive" "/releases/tags/release-" + version) if beta: version += " beta" try: content = requests.get(url) except requests.HTTPError as exc: if exc.response.status_code == 404: log.error("[%s] Release does not exist", version) else: log.exception( "[%s] Network error while fetching release notes", version) return except: log.exception("[%s] Unknown error while fetching release notes", version) return try: data = content.json() except ValueError: log.exception("[%s] Invalid release notes", version) return finally: del content try: html = markdown(data["body"]) except KeyError: log.error("[%s] Release notes is missing its body", version) return except (UnicodeDecodeError, ValueError): log.exception("[%s] Release notes conversion error", version) return dialog = QDialog() dialog.setWindowTitle( f"{self.manager.app_name} {version} - Release notes") dialog.setWindowIcon(QIcon(self.get_window_icon())) dialog.resize(600, 400) notes = QTextEdit() notes.setStyleSheet("background-color: #eee; border: none;") notes.setReadOnly(True) notes.setHtml(html) buttons = QDialogButtonBox() buttons.setStandardButtons(QDialogButtonBox.Ok) buttons.clicked.connect(dialog.accept) layout = QVBoxLayout() layout.addWidget(notes) layout.addWidget(buttons) dialog.setLayout(layout) dialog.exec_() def show_metadata(self, file_path: str) -> None: self.manager.ctx_edit_metadata(file_path) def setup_systray(self) -> None: icons = {} for state in { "conflict", "disabled", "error", "idle", "notification", "paused", "syncing", "update", }: name = "{}{}.svg".format(state, "_light" if WINDOWS else "") icon = QIcon() icon.addFile(find_icon(name)) if MAC: icon.addFile(find_icon("active.svg"), mode=QIcon.Selected) icons[state] = icon setattr(self, "icons", icons) self.tray_icon = DriveSystrayIcon(self) if not self.tray_icon.isSystemTrayAvailable(): log.critical("There is no system tray available!") else: self.tray_icon.setToolTip(self.manager.app_name) self.set_icon_state("disabled") self.tray_icon.show() def event(self, event: QEvent) -> bool: """ Handle URL scheme events under macOS. """ url = getattr(event, "url", None) if not url: # This is not an event for us! return super().event(event) try: final_url = unquote(event.url().toString()) return self._handle_nxdrive_url(final_url) except: log.exception("Error handling URL event %r", url) return False def _handle_nxdrive_url(self, url: str) -> bool: """ Handle an nxdrive protocol URL. """ info = parse_protocol_url(url) if not info: return False cmd = info["command"] path = info.get("filepath", None) manager = self.manager log.debug("Event URL=%s, info=%r", url, info) # Event fired by a context menu item func = { "access-online": manager.ctx_access_online, "copy-share-link": manager.ctx_copy_share_link, "edit-metadata": manager.ctx_edit_metadata, }.get(cmd, None) if func: func(path) elif "edit" in cmd: manager.direct_edit.edit( info["server_url"], info["doc_id"], user=info["user"], download_url=info["download_url"], ) elif cmd == "trigger-watch": for engine in manager._engine_definitions: manager.osi.watch_folder(engine.local_folder) elif cmd == "token": self.api.handle_token(info["token"], info["username"]) else: log.warning("Unknown event URL=%r, info=%r", url, info) return False return True @if_frozen def init_nxdrive_listener(self) -> None: """ Set up a QLocalServer to listen to nxdrive protocol calls. On Windows, when an nxdrive:// URL is opened, it creates a new instance of Nuxeo Drive. As we want the already running instance to receive this call (particularly during the login process), we set up a QLocalServer in that instance to listen to the new ones who will send their data. The Qt implementation of QLocalSocket on Windows makes use of named pipes. We just need to connect a handler to the newConnection signal to process the URLs. """ self._nxdrive_listener = QLocalServer() self._nxdrive_listener.newConnection.connect(self._handle_connection) self._nxdrive_listener.listen("com.nuxeo.drive.protocol") self.aboutToQuit.connect(self._nxdrive_listener.close) @if_frozen def _handle_connection(self) -> None: """ Retrieve the connection with other instances and handle the incoming data. """ con: QLocalSocket = self._nxdrive_listener.nextPendingConnection() log.debug("Receiving socket connection for nxdrive protocol handling") if not con or not con.waitForConnected(): log.error(f"Unable to open server socket: {con.errorString()}") return if con.waitForReadyRead(): payload = con.readAll() url = force_decode(payload.data()) self._handle_nxdrive_url(url) con.disconnectFromServer() con.waitForDisconnected() del con log.debug("Successfully closed server socket") # Bring settings window to front self.settings_window.show() def update_status(self, engine: "Engine") -> None: """ Update the systray status for synchronization, conflicts/errors and software updates. """ sync_state = error_state = update_state = "" update_state = self.manager.updater.status self.refresh_conflicts(engine.uid) # Check synchronization state if engine.is_paused(): sync_state = "suspended" elif engine.is_syncing(): sync_state = "syncing" # Check error state if engine.has_invalid_credentials(): error_state = "auth_expired" elif self.conflicts_model.count: error_state = "conflicted" elif self.errors_model.count: error_state = "error" self._window_root(self.systray_window).setStatus.emit( sync_state, error_state, update_state) @pyqtSlot(str) def get_last_files(self, uid: str) -> None: files = self.api.get_last_files(uid, 10, "", None) self.file_model.empty() self.file_model.addFiles(files) def current_language(self) -> Optional[str]: lang = Translator.locale() for tag, name in self.language_model.languages: if tag == lang: return name return None
from PyQt5.QtQml import QQmlContext, qmlRegisterType from PyQt5.QtQuick import QQuickView from PyQt5.QtCore import QUrl from context import Context from PyQt5.QtQml import QQmlListProperty from plateau import Plateau from case import Case from qplateau import QPlateau from qcase import QCase if __name__ == "__main__": app = QGuiApplication(sys.argv) myApp = Context() view = QQuickView() view.resize(800, 800) view.setResizeMode(QQuickView.SizeRootObjectToView) qmlRegisterType(QPlateau, 'temp', 1, 0, 'QPlateau') qmlRegisterType(QCase, 'temp0', 1, 0, 'QCase') ctx = view.rootContext() myApp.setContext( ctx ) view.setSource(QUrl("qml/main.qml")) view.show() sys.exit(app.exec_())