Beispiel #1
0
class MainWindow(QMainWindow):
    def __init__(self, app):
        QMainWindow.__init__(self)

        # Config app icon
        self.app_icon = QtGui.QIcon("assets/icon.png")
        self.setWindowIcon(self.app_icon)

        self.tray_icon = QSystemTrayIcon(self.app_icon, self)
        self.tray_icon.activated.connect(self.tray_icon_event)
        menu = QMenu(self)
        quit_action = menu.addAction("Fechar aplicação")
        quit_action.triggered.connect(app.quit)
        tray_menu = QMenu()
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

    def tray_icon_event(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if not self.isVisible():
                self.show()

    def hide_window_and_tray(self):
        if self.tray_icon.isVisible():
            self.tray_icon.hide()
        if self.isVisible():
            self.hide()

    # When user clicks on window quit button
    def closeEvent(self, event):
        event.ignore()
        self.hide()
        self.tray_icon.showMessage("File upload", "Aplicação foi minimizada", QSystemTrayIcon.Information, 1000)
Beispiel #2
0
class main(QDialog):
    #托盘
    def __init__(self):
        super().__init__()
        self.loadMenu()
        self.initUI()

    def loadMenu(self):
        menuItems = []
        menuItems.append({"text": "启动", "icon": "./icon/launch.png", "event": self.show, "hot": "D"})
        menuItems.append({"text": "退出", "icon": "./icon/car.png", "event": self.close, "hot": "Q"})
        self.trayIconMenu = QMenu(self)
        for i in menuItems:
             tmp = QAction(QIcon(i["icon"]), i["text"],self, triggered=i["event"])
             tmp.setShortcut(self.tr(i["hot"]))
             self.trayIconMenu.addAction(tmp)

    def initUI(self):
        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setIcon(QIcon("./icon/car.png"))
        self.trayIcon.setContextMenu(self.trayIconMenu)
        self.trayIcon.show()
        self.setWindowIcon(QIcon("./icon/car.png"))
        self.setGeometry(300, 300, 180, 300)
        self.setWindowTitle('窗体标题')
    def closeEvent(self, event):
        if self.trayIcon.isVisible():
            self.trayIcon.hide()
class Tray:

    def __init__(self, target_platform: QMainWindow):

        self.target_platform = target_platform
        self.tray_icon = QSystemTrayIcon()

        show_action = QAction("Show", self.target_platform)
        quit_action = QAction("Exit", self.target_platform)

        show_action.triggered.connect(self.target_platform_show)
        quit_action.triggered.connect(self.target_platform_close)

        tray_menu = QMenu()

        tray_menu.addAction(show_action)
        tray_menu.addAction(quit_action)

        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.setIcon(QIcon(os.path.join(Common.current_directory(), 'tray.png')))

    def target_platform_show(self):
        self.target_platform.show()
        self.tray_icon.hide()

    def target_platform_close(self):
        self.target_platform.close()

    def show(self):
        self.tray_icon.show()

    def hide(self):
        self.tray_icon.hide()
        self.target_platform.show()
Beispiel #4
0
class QTGui(QWidget):


    def __init__(self):
        super().__init__()
        self.showWindow()

    def changeEvent(self, QEvent):
        if QEvent.type() == QEvent.WindowStateChange:
            if self.isMinimized():
                print("minimized")
                self.minimizetotray()
        super().changeEvent(QEvent)

    def showWindow(self):
        self.setGeometry(300, 300, 300, 63)
        self.setFixedSize(self.size())
        self.setWindowIcon(QIcon("icon.png"))
        self.setWindowTitle("pyWall UI")

        global btn
        btn = QPushButton("Change", self)
        btn.resize(75, 23)
        btn.move(0, self.height() - btn.height())
        btn.setToolTip("Change the wallpaper right now.")
        btn.clicked.connect(newWallpaperInNewThread)
        global txtinterval
        txtinterval = QTextEdit("100", self)
        txtinterval.setToolTip("Time interval in seconds between wallpaper changes.")
        txtinterval.resize(70, 23)
        txtinterval.move(0, btn.y() - txtinterval.height())
        global chkbox
        chkbox = QCheckBox("Timer", self)
        chkbox.setToolTip("Use timer for auto wallpaper change.")
        chkbox.resize(49, 17)
        chkbox.move(0, txtinterval.y() - chkbox.height())
        chkbox.stateChanged.connect(checkBoxStateChanged)
        global label
        label = QLabel("", self)
        label.setFont(QFont("Times", 8, QFont.Bold))
        label.move(btn.width() + 5, 0)
        label.resize(self.width()-btn.width(),self.height())
        label.setWordWrap(True)
        self.show()

    def minimizetotray(self):
        self.hide()
        self.tray = QSystemTrayIcon()
        self.tray.setIcon(QIcon("icon.png"))
        self.tray.setToolTip("pyWall Tray")
        self.tray.show()
        self.tray.showMessage("pyWall", "pyWall will run in background.", msecs=500)
        self.tray.activated.connect(self.trayiconactivated)

    def trayiconactivated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.tray.hide()
            self.show()
Beispiel #5
0
class MainWindow(QMainWindow):
    tray_icon = None

    def __init__(self):
        QMainWindow.__init__(self)
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(QIcon('icon.png'))
        self.hide()

        central_widget = QWidget(self)
        self.setCentralWidget(central_widget)

        quit_action = QAction("quit", self)
        quit_action.triggered.connect(qApp.quit)
        tray_menu = QMenu()
        tray_menu.addAction(quit_action)

        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

        self.pouple = Pouple()
        self.history = History()
        self.bind_keys()

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.tray_icon.hide()

    def command(self, cmd):
        def execute():
            print(cmd.__name__)
            self.history.add(cmd)
            cmd()

        return execute

    def bind_keys(self):
        cfg = configparser.RawConfigParser()
        cfg.read('settings.cfg')

        hot_keys = {
            'align_left': self.pouple.align_left,
            'align_right': self.pouple.align_right,
            'align_top': self.pouple.align_top,
            'align_bottom': self.pouple.align_bottom,
            'center': self.pouple.center,
            'screen': self.pouple.screen,
            'fullscreen': self.pouple.fullscreen,
            'undo': self.history.undo,
            'redo': self.history.redo,
        }
        for name in hot_keys:
            keyboard.add_hotkey(cfg.get('keys', name), self.command(hot_keys[name]))
Beispiel #6
0
class Main(QDialog):
    def __init__(self):
        super().__init__()
        self.loadMenu()
        self.initUI()

    def loadMenu(self):
        menuItems = []  # 菜单列表
        menuItems.append({
            "text": "启动",
            "icon": "./icons/set.png",
            "event": self.show,
            "hot": "D"
        })
        menuItems.append({
            "text": "退出",
            "icon": "./icons/close.png",
            "event": self.close,
            "hot": "Q"
        })
        self.trayIconMenu = QMenu(self)  # 创建菜单
        #遍历绑定 显示的文字、图标、热键和点击事件
        #热键可能是无效的 我这里只是为了显示效果而已
        for i in menuItems:
            tmp = QAction(QIcon(i["icon"]),
                          i["text"],
                          self,
                          triggered=i["event"])
            tmp.setShortcut(self.tr(i["hot"]))
            self.trayIconMenu.addAction(tmp)

    def initUI(self):
        self.trayIcon = QSystemTrayIcon(self)  # <===创建通知栏托盘图标
        self.trayIcon.setIcon(QIcon("./joyrun/request/pic.ico"))  #<===设置托盘图标

        self.trayIcon.setContextMenu(self.trayIconMenu)  #<===创建右键连接菜单
        self.trayIcon.show()  #<====显示托盘

        self.setWindowIcon(QIcon("./joyrun/request/pic.ico"))  #<===设置窗体图标
        self.setGeometry(300, 300, 180, 300)  # <===设置窗体打开位置与宽高
        self.setWindowTitle('窗体标题')

        self.show()  #<====显示窗体
        # self.hide()#<====隐藏窗体
        # 默认不显示窗体

    # 重写窗体关闭事件,让其点击关闭时隐藏
    def closeEvent(self, event):
        if self.trayIcon.isVisible():
            self.trayIcon.hide()
Beispiel #7
0
class AppWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(self.style().standardIcon(
            QStyle.SP_ComputerIcon))
        show_action = QAction("Show", self)
        quit_action = QAction("Exit", self)
        hide_action = QAction("Hide", self)
        show_action.triggered.connect(self.show)
        hide_action.triggered.connect(self.hide)
        quit_action.triggered.connect(self.close)
        tray_menu = QMenu()
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()
        self.tray_icon.activated.connect(self.hand_tray_activated)
        self.ui = Control_Panel()
        self.ui.setupUi(self)
        self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint
                            | QtCore.Qt.WindowMinimizeButtonHint)
        self.show()
        self.ui.th.hide_signal.connect(self.minimize_to_tray)

    def hand_tray_activated(self, reason):
        # On double clicking the tray icon the windows is shown again
        if reason == QSystemTrayIcon.DoubleClick:
            self.show()

    def minimize_to_tray(self):
        # when go button is clicked the app is minimized to tray
        self.hide()
        self.tray_icon.showMessage("Hand of The King",
                                   "Application was minimized to tray",
                                   QSystemTrayIcon.Information, 2000)

    def closeEvent(self, event):
        # overriding the closeEvent function of the window to cleanup all open windows and release the camera
        self.tray_icon.hide()
        self.ui.application_on = False
        self.ui.gesture_detector.camera.release()
        self.ui.hand_window.close()
        event.accept()
class MainWindow(QMainWindow):
    """
         Сheckbox and system tray icons.
         Will initialize in the constructor.
    """
    check_box = None
    tray_icon = None

    # Override the class constructor
    def __init__(self):
        # Be sure to call the super class method
        QMainWindow.__init__(self)

        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(self.style().standardIcon(
            QStyle.SP_ComputerIcon))
        show_action = QAction("Show", self)
        quit_action = QAction("Exit", self)
        hide_action = QAction("Hide", self)
        show_action.triggered.connect(self.show)
        hide_action.triggered.connect(self.hide)
        quit_action.triggered.connect(self.quit)
        tray_menu = QMenu()
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

    def quit(self):
        self.tray_icon.hide()
        qApp.quit()

    # Override closeEvent, to intercept the window closing event
    # The window will be closed only if there is no check mark in the check box
    def closeEvent(self, event):
        event.ignore()
        self.hide()

        self.tray_icon.showMessage("Tray Program",
                                   "Application was minimized to Tray",
                                   QSystemTrayIcon.Information, 2000)
Beispiel #9
0
class BaseTray(object):
    def __init__(self, icon_path):
        self.tray = QSystemTrayIcon()
        self.tray.setIcon(QIcon(icon_path))
        self.tray_menu = QMenu()
        self._set_menu()

    def _set_menu(self):
        pass

    def show_message(self, text):
        icon = self.tray.MessageIcon()
        self.tray.showMessage('message', text, icon, 1000)

    def show(self):
        self.tray.show()

    def quit(self):
        self.tray.hide()
        sys.exit()
Beispiel #10
0
class Window(QWidget):
    def __init__(self, *args, **kwargs):
        super(Window, self).__init__(*args, **kwargs)
        layout = QHBoxLayout(self)
        layout.addWidget(QPushButton('开始闪烁', self, clicked=self.start_flash))
        layout.addWidget(QPushButton('停止闪烁', self, clicked=self.stop_flash))
        # 创建托盘图标
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(self.style().standardIcon(
            QStyle.SP_ComputerIcon))
        self.tray_icon.show()
        # 图标闪烁定时器
        self.tray_visible = True
        self.flash_timer = QTimer(self, timeout=self.flash_icon)

    def closeEvent(self, event):
        self.stop_flash()
        self.tray_icon.hide()
        super(Window, self).closeEvent(event)

    def start_flash(self):
        """开始闪烁"""
        if not self.flash_timer.isActive():
            self.flash_timer.start(500)

    def stop_flash(self):
        """停止闪烁后需要显示图标"""
        if self.flash_timer.isActive():
            self.flash_timer.stop()
            self.tray_icon.setIcon(self.style().standardIcon(
                QStyle.SP_ComputerIcon))

    def flash_icon(self):
        """根据当前图标是否可见切换图标"""
        if self.tray_visible:
            self.tray_icon.setIcon(self.style().standardIcon(
                QStyle.SP_TrashIcon))
        else:
            self.tray_icon.setIcon(self.style().standardIcon(
                QStyle.SP_ComputerIcon))
        self.tray_visible = not self.tray_visible
Beispiel #11
0
class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)

        self.tray_icon = QSystemTrayIcon(self)

        # Set icon to a standard or custom icon
        self.tray_icon.setIcon(self.style().standardIcon(
            QStyle.SP_ComputerIcon))
        # self.tray_icon.setIcon(QtGui.QIcon("icons/devdungeon32x32.png"))

        exit_action = QAction("Exit", self)
        exit_action.triggered.connect(self.exit_app)

        tray_menu = QMenu()
        tray_menu.addAction(exit_action)
        self.tray_icon.setContextMenu(tray_menu)  # Set right-click menu
        self.tray_icon.show()

    def notify(self, message):
        """Generate a desktop notification"""
        self.tray_icon.showMessage("Pssst!", message,
                                   QSystemTrayIcon.Information, 3000)

    def exit_app(self):
        self.tray_icon.hide(
        )  # Do this or icon will linger until you hover after exit
        qApp.quit()

    def closeEvent(self, event):
        """
        By overriding closeEvent, we can ignore the event and instead
        hide the window, effectively performing a "close-to-system-tray"
        action. To exit, the right-click->Exit option from the system
        tray must be used.
        """
        event.ignore()
        self.hide()
        self.notify("App minimize to system tray.")
Beispiel #12
0
class DlgMain(QDialog):
    def addSystemTray(self):
        minimizeAction = QAction("Mi&nimize", self, triggered=self.hide)
        maximizeAction = QAction("Ma&ximize",
                                 self,
                                 triggered=self.showMaximized)
        restoreAction = QAction("&Restore", self, triggered=self.showNormal)
        quitAction = QAction("&Quit", self, triggered=self.close)
        self.trayIconMenu = QMenu(self)
        self.trayIconMenu.addAction(minimizeAction)
        self.trayIconMenu.addAction(maximizeAction)
        self.trayIconMenu.addAction(restoreAction)
        self.trayIconMenu.addSeparator()
        self.trayIconMenu.addAction(quitAction)
        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setIcon(QIcon("skin/icons/logo.png"))
        self.setWindowIcon(QIcon("skin/icons/logo.png"))
        self.trayIcon.setContextMenu(self.trayIconMenu)
        self.trayIcon.show()
        sys.exit(self.exec_())

    def closeEvent(self, event):
        if self.trayIcon.isVisible():
            self.trayIcon.hide()
Beispiel #13
0
class MainWindow(MainWindow_Ui):
    def __init__(self):
        super().__init__()
        self.statusbar.showMessage('Please Wait ...')

#threads     
        self.threadPool=[]
#starting aria
        start_aria = StartAria2Thread()
        self.threadPool.append(start_aria)
        self.threadPool[0].start() 
        self.threadPool[0].ARIA2RESPONDSIGNAL.connect(self.startAriaMessage)

#initializing    

#add downloads to the download_table
        f_download_list_file = Open(download_list_file)
        download_list_file_lines = f_download_list_file.readlines()
        f_download_list_file.close()
            
        for line in download_list_file_lines:
            gid = line.strip()
            self.download_table.insertRow(0)
            download_info_file = download_info_folder + "/" + gid
            f = Open(download_info_file)
            download_info_file_lines = f.readlines()
            f.close()
            for i in range(10):
                item = QTableWidgetItem(download_info_file_lines[i].strip())
                self.download_table.setItem(0 , i , item)

        row_numbers = self.download_table.rowCount()
        for row in range(row_numbers):
            status = self.download_table.item(row , 1).text() 
            if (status != "complete" and status != "error"):
                gid = self.download_table.item(row,8).text() 
                add_link_dictionary_str = self.download_table.item(row,9).text() 
                add_link_dictionary = ast.literal_eval(add_link_dictionary_str.strip()) 
                add_link_dictionary['start_hour'] = None
                add_link_dictionary['start_minute'] = None
                add_link_dictionary['end_hour'] = None
                add_link_dictionary['end_minute'] = None
                add_link_dictionary['after_download'] = 'None'

                download_info_file = download_info_folder + "/" + gid
                f = Open(download_info_file)
                download_info_file_lines = f.readlines()
                f.close()

                f = Open(download_info_file , "w")
                for i in range(10):
                    if i == 1 :
                        f.writelines("stopped" + "\n")
                        item = QTableWidgetItem('stopped')
                        self.download_table.setItem(row , i , item )
                    elif i == 9 :
                        f.writelines(str(add_link_dictionary) + "\n")
                        item = QTableWidgetItem(str(add_link_dictionary))
                        self.download_table.setItem(row,i , item)
                    else:
                        f.writelines(download_info_file_lines[i].strip() + "\n")

                f.close()
        self.addlinkwindows_list = []
        self.propertieswindows_list = []
        self.progress_window_list = []
        self.progress_window_list_dict = {}

        check_download_info = CheckDownloadInfoThread()
        self.threadPool.append(check_download_info)
        self.threadPool[1].start()
        self.threadPool[1].DOWNLOAD_INFO_SIGNAL.connect(self.checkDownloadInfo)

        check_selected_row = CheckSelectedRowThread()
        self.threadPool.append(check_selected_row)
        self.threadPool[2].start()
        self.threadPool[2].CHECKSELECTEDROWSIGNAL.connect(self.checkSelectedRow)

        check_flashgot = CheckFlashgot()
        self.threadPool.append(check_flashgot)
        self.threadPool[3].start()
        self.threadPool[3].CHECKFLASHGOTSIGNAL.connect(self.checkFlashgot)

        

 

        self.system_tray_icon = QSystemTrayIcon() 
        self.system_tray_icon.setIcon(QIcon('icon'))
        system_tray_menu = QMenu()
        system_tray_menu.addAction(self.addlinkAction)
        system_tray_menu.addAction(self.pauseAllAction)
        system_tray_menu.addAction(self.stopAllAction)
        system_tray_menu.addAction(self.minimizeAction)
        system_tray_menu.addAction(self.exitAction)
        self.system_tray_icon.setContextMenu(system_tray_menu)
        self.system_tray_icon.activated.connect(self.systemTrayPressed)
        self.system_tray_icon.show()

    def startAriaMessage(self,message):
        global aria_startup_answer
        if message == 'yes':
            sleep (2)
            self.statusbar.showMessage('Ready...')
            aria_startup_answer = 'ready'
        else:
            self.statusbar.showMessage('Error...')
            notifySend('Persepolis can not connect to Aria2' , 'Restart Persepolis' ,10000,'critical' )

    def checkDownloadInfo(self,gid):
        try:
#get download information from download_info_file according to gid and write them in download_table cells
            download_info_file = config_folder + "/download_info/" + gid
            f = Open(download_info_file)
            download_info_file_lines = f.readlines()
            f.close()
#finding row of this gid!
            for i in range(self.download_table.rowCount()):
                row_gid = self.download_table.item(i , 8).text()
                if gid == row_gid :
                    row = i 
                    break

            for i in range(10):
#remove gid of completed download from active downloads list file
                if i == 1 :
                    status = download_info_file_lines[i].strip()
                    status = str(status)
                    status_download_table = str(self.download_table.item(row , 1 ) . text())

                    if status == "complete":
                        f = Open(download_list_file_active)
                        download_list_file_active_lines = f.readlines()
                        f.close()
                        f = Open(download_list_file_active , "w")
                        for line in download_list_file_active_lines :
                            if line.strip() != gid :
                                f.writelines(line.strip() + "\n")
                        f.close()
                    
#update download_table cells
                item = QTableWidgetItem(download_info_file_lines[i].strip())
                self.download_table.setItem(row , i , item)
                self.download_table.viewport().update()
#update progresswindow
            try :
            
                member_number = self.progress_window_list_dict[gid]
                progress_window = self.progress_window_list[member_number]
                #link
                add_link_dictionary_str = str(download_info_file_lines[9].strip())
                add_link_dictionary = ast.literal_eval(add_link_dictionary_str) 
                link = "<b>Link</b> : " +  str(add_link_dictionary ['link'])
                progress_window.link_label.setText(link)
                progress_window.setToolTip(link)

                #Save as
                final_download_path = add_link_dictionary['final_download_path']
                if final_download_path == None :
                    final_download_path = str(add_link_dictionary['download_path'])
                        
                save_as = "<b>Save as</b> : " + final_download_path + "/" + str(download_info_file_lines[0].strip())
                progress_window.save_label.setText(save_as)
                file_name = str(download_info_file_lines[0].strip())
                if file_name != "***":
                    progress_window.setWindowTitle(file_name ) 

                #status
                progress_window.status = download_info_file_lines[1].strip()
                status = "<b>status</b> : " + progress_window.status 
                progress_window.status_label.setText(status)
                if progress_window.status == "downloading":
                    progress_window.resume_pushButton.setEnabled(False)
                    progress_window.stop_pushButton.setEnabled(True)
                    progress_window.pause_pushButton.setEnabled(True)
                elif progress_window.status == "paused":
                    progress_window.resume_pushButton.setEnabled(True)
                    progress_window.stop_pushButton.setEnabled(True)
                    progress_window.pause_pushButton.setEnabled(False)
                elif progress_window.status == "waiting":
                    progress_window.resume_pushButton.setEnabled(False)
                    progress_window.stop_pushButton.setEnabled(False)
                    progress_window.pause_pushButton.setEnabled(False)
                elif progress_window.status == "scheduled":
                    progress_window.resume_pushButton.setEnabled(False)
                    progress_window.stop_pushButton.setEnabled(True)
                    progress_window.pause_pushButton.setEnabled(False)
                elif progress_window.status == "stopped" or progress_window.status == "error" or progress_window.status == "complete" :
                    progress_window.close()
                    self.progress_window_list[member_number] = []
                    del self.progress_window_list_dict[gid]
                    if progress_window.status == "complete":
                        notifySend("Download Complete" ,str(download_info_file_lines[0])  , 10000 , 'ok' )
                    elif progress_window.status == "stopped":
                        notifySend("Download Stopped" , str(download_info_file_lines[0]) , 10000 , 'no')

                    elif progress_window.status == "error":
                        notifySend("Download Error" , str(download_info_file_lines[0]) , 10000 , 'fail')
               
                        add_link_dictionary['start_hour'] = None
                        add_link_dictionary['start_minute'] = None
                        add_link_dictionary['end_hour'] = None
                        add_link_dictionary['end_minute'] = None
                        add_link_dictionary['after_download'] = 'None'

                        f = Open(download_info_file , "w")
                        for i in range(10):
                            if i == 9 :
                                f.writelines(str(add_link_dictionary) + "\n")
                            else:
                                f.writelines(download_info_file_lines[i].strip() + "\n")

                        f.close()
                    
                    if os.path.isfile('/tmp/persepolis/shutdown/' + gid ) == True and progress_window.status != 'stopped':
                        answer = download.shutDown()
                        if answer == 'error':
                            os.system('killall aria2c')
                        f = Open('/tmp/persepolis/shutdown/' + gid , 'w')
                        f.writelines('shutdown')
                        f.close()
                    elif os.path.isfile('/tmp/persepolis/shutdown/' + gid ) == True and progress_window.status == 'stopped':
                        f = Open('/tmp/persepolis/shutdown/' + gid , 'w')
                        f.writelines('canceled')
                        f.close()



             
                #downloaded
                downloaded = "<b>Downloaded</b> : " + str(download_info_file_lines[3].strip()) + "/" + str(download_info_file_lines[2].strip())
                progress_window.downloaded_label.setText(downloaded)

                #Transfer rate
                rate = "<b>Transfer rate</b> : " + str(download_info_file_lines[6].strip())
                progress_window.rate_label.setText(rate)

                #Estimate time left
                estimate_time_left = "<b>Estimate time left</b> : " + str(download_info_file_lines[7].strip()) 
                progress_window.time_label.setText(estimate_time_left)

                #Connections
                connections = "<b>Connections</b> : " + str(download_info_file_lines[5].strip())
                progress_window.connections_label.setText(connections)


                #progressbar
                value = download_info_file_lines[4].strip()
                value = value[:-1]
                progress_window.download_progressBar.setValue(int(value))
            except :
                pass
        except:
            pass
                   



#contex menu
    def contextMenuEvent(self, event):
        self.tablewidget_menu = QMenu(self)
        self.tablewidget_menu.addAction(self.resumeAction)
        self.tablewidget_menu.addAction(self.pauseAction)
        self.tablewidget_menu.addAction(self.stopAction)
        self.tablewidget_menu.addAction(self.removeAction)
        self.tablewidget_menu.addAction(self.propertiesAction)
        self.tablewidget_menu.addAction(self.progressAction)
        self.tablewidget_menu.popup(QtGui.QCursor.pos())
#drag and drop for links
    def dragEnterEvent(self, droplink):

        text = str(droplink.mimeData().text())
      
        if ("tp:/" in text[2:6]) or ("tps:/" in text[2:7]) :
            droplink.accept()
        else:
            droplink.ignore() 

    def dropEvent(self, droplink):
        link_clipborad = QApplication.clipboard()
        link_clipborad.clear(mode=link_clipborad.Clipboard )
        link_string = droplink.mimeData().text() 
        link_clipborad.setText(str(link_string), mode=link_clipborad.Clipboard) 
        self.addLinkButtonPressed(button =link_clipborad )
    
	
    def gidGenerator(self):
        my_gid = hex(random.randint(1152921504606846976,18446744073709551615))
        my_gid = my_gid[2:18]
        my_gid = str(my_gid)
        f = Open(download_list_file_active)
        active_gid_list = f.readlines()
        f.close()
        while my_gid in active_gid_list :
            my_gid = self.gidGenerator()
        active_gids = download.activeDownloads()
        while my_gid in active_gids:
            my_gid = self.gidGenerator()
        
        return my_gid

    def selectedRow(self):
        try:
            item = self.download_table.selectedItems()
            selected_row_return = self.download_table.row(item[1]) 
            download_info = self.download_table.item(selected_row_return , 9).text()
            download_info = ast.literal_eval(download_info) 
            link = download_info['link']
            self.statusbar.showMessage(str(link))

        except :
            selected_row_return = None

        return selected_row_return 

    def checkSelectedRow(self):
        try:
            item = self.download_table.selectedItems()
            selected_row_return = self.download_table.row(item[1]) 
        except :
            selected_row_return = None

        if selected_row_return != None :
            status = self.download_table.item(selected_row_return , 1).text() 
            if status == "scheduled":
                self.resumeAction.setEnabled(False)
                self.pauseAction.setEnabled(False)
                self.stopAction.setEnabled(True)
                self.removeAction.setEnabled(False)
                self.propertiesAction.setEnabled(False)
                self.progressAction.setEnabled(True)
            elif status == "stopped" or status == "error" :
                self.stopAction.setEnabled(False)
                self.pauseAction.setEnabled(False)
                self.resumeAction.setEnabled(True)
                self.removeAction.setEnabled(True)
                self.propertiesAction.setEnabled(True)
                self.progressAction.setEnabled(False)
            elif status == "downloading":
                self.resumeAction.setEnabled(False)
                self.pauseAction.setEnabled(True)
                self.stopAction.setEnabled(True)
                self.removeAction.setEnabled(False)
                self.propertiesAction.setEnabled(False)
                self.progressAction.setEnabled(True)
            elif status == "waiting": 
                self.stopAction.setEnabled(False)
                self.resumeAction.setEnabled(False)
                self.pauseAction.setEnabled(False)
                self.removeAction.setEnabled(False)
                self.propertiesAction.setEnabled(False)
                self.progressAction.setEnabled(True)
            elif status == "complete":
                self.stopAction.setEnabled(False)
                self.resumeAction.setEnabled(False)
                self.pauseAction.setEnabled(False)
                self.removeAction.setEnabled(True)
                self.propertiesAction.setEnabled(True)
                self.progressAction.setEnabled(False)
            elif status == "paused":
                self.stopAction.setEnabled(True)
                self.resumeAction.setEnabled(True)
                self.pauseAction.setEnabled(False)
                self.removeAction.setEnabled(False)
                self.propertiesAction.setEnabled(False)
                self.progressAction.setEnabled(True)
                
 
            else:
                self.resumeAction.setEnabled(True)
                self.stopAction.setEnabled(True)
                self.pauseAction.setEnabled(True)
                self.propertiesAction.setEnabled(True)
        else:
            self.resumeAction.setEnabled(True)
            self.stopAction.setEnabled(True)
            self.pauseAction.setEnabled(True)
            self.removeAction.setEnabled(True)
            self.propertiesAction.setEnabled(True)

           
    def checkFlashgot(self):
        sleep(0.5)
        flashgot_file = Open("/tmp/persepolis-flashgot")
        flashgot_line = flashgot_file.readlines()
        flashgot_file.close()
        flashgot_file.remove()
        flashgot_add_link_dictionary_str = flashgot_line[0]
        flashgot_add_link_dictionary = ast.literal_eval(flashgot_add_link_dictionary_str) 
        self.flashgotAddLink(flashgot_add_link_dictionary)


    def flashgotAddLink(self,flashgot_add_link_dictionary):
        addlinkwindow = AddLinkWindow(self.callBack , flashgot_add_link_dictionary)
        self.addlinkwindows_list.append(addlinkwindow)
        self.addlinkwindows_list[len(self.addlinkwindows_list) - 1].show()

       
            



    def addLinkButtonPressed(self ,button):
        addlinkwindow = AddLinkWindow(self.callBack)
        self.addlinkwindows_list.append(addlinkwindow)
        self.addlinkwindows_list[len(self.addlinkwindows_list) - 1].show()

    def callBack(self , add_link_dictionary):
        gid = self.gidGenerator()

        download_info_file_list = ['***','waiting','***','***','***','***','***','***',gid , str(add_link_dictionary)]
        download_info_file = config_folder + "/download_info/" + gid
        os.system("touch " + download_info_file )
        f = Open(download_info_file , "w")
        for i in range(10):
            f.writelines(download_info_file_list[i] + "\n")

        f.close()
        
        self.download_table.insertRow(0)
        j = 0
        for i in download_info_file_list :
            item = QTableWidgetItem(i)
            self.download_table.setItem(0,j,item)
            j = j + 1

        f = Open (download_list_file , "a")
        f.writelines(gid + "\n")
        f.close()


        f = Open (download_list_file_active , "a")
        f.writelines(gid + "\n")
        f.close()
        new_download = DownloadLink(gid)
        self.threadPool.append(new_download)
        self.threadPool[len(self.threadPool) - 1].start()
        self.progressBarOpen(gid) 
        if add_link_dictionary['start_hour'] == None :
            message = "Download Starts"
        else:
            message = "Download Scheduled"
        notifySend(message ,'' , 10000 , 'no')

 

        
    def resumeButtonPressed(self,button):
        selected_row_return = self.selectedRow()
        if selected_row_return != None:
            gid = self.download_table.item(selected_row_return , 8 ).text()
            download_status = self.download_table.item(selected_row_return , 1).text()
 
                
            if download_status == "paused" :
                answer = download.downloadUnpause(gid)
                if answer == 'None':
                    notifySend("Aria2 did not respond!","Try agian!",10000,'warning' )



            else:
                new_download = DownloadLink(gid)
                self.threadPool.append(new_download)
                self.threadPool[len(self.threadPool) - 1].start()
                sleep(1)
                self.progressBarOpen(gid)



        else:
            self.statusbar.showMessage("Please select an item first!")


    def stopButtonPressed(self,button):
        selected_row_return = self.selectedRow()
        if selected_row_return != None:
            gid = self.download_table.item(selected_row_return , 8 ).text()
            answer = download.downloadStop(gid)
            if answer == 'None':
                notifySend("Aria2 did not respond!","Try agian!" , 10000 , 'critical' )



           
               
        else:
            self.statusbar.showMessage("Please select an item first!")

    def pauseButtonPressed(self,button):
        selected_row_return = self.selectedRow()
        if selected_row_return != None:
            gid = self.download_table.item(selected_row_return , 8 ).text()
            answer = download.downloadPause(gid)
            if answer == 'None':
                notifySend("Aria2 did not respond!" , "Try agian!" , 10000 , 'critical' )

        else:
            self.statusbar.showMessage("Please select an item first!")
        sleep(1)

    def removeButtonPressed(self,button):
        self.removeAction.setEnabled(False)
        selected_row_return = self.selectedRow()
        if selected_row_return != None:
            gid = self.download_table.item(selected_row_return , 8 ).text()
            try:
                file_name = self.download_table.item(selected_row_return , 0).text()
            except:
                file_name = None 
            sleep(0.5)
            self.download_table.removeRow(selected_row_return)

#remove gid of download from download list file
            f = Open(download_list_file)
            download_list_file_lines = f.readlines()
            f.close()
            f = Open(download_list_file , "w")
            for i in download_list_file_lines:
                if i.strip() != gid:
                    f.writelines(i.strip() + "\n")
            f.close()
#remove gid of download from active download list file
            f = Open(download_list_file_active)
            download_list_file_active_lines = f.readlines()
            f.close()
            f = Open(download_list_file_active , "w")
            for i in download_list_file_active_lines:
                if i.strip() != gid:
                    f.writelines(i.strip() + "\n")
            f.close()
#remove download_info_file
            download_info_file = download_info_folder + "/" + gid
            f = Open(download_info_file)
            f.close()
            f.remove()
#remove file of download form download temp folder
            if file_name != None :
                file_name_path = temp_download_folder + "/" +  str(file_name)
                os.system('rm "' + str(file_name_path) +'"')
                file_name_aria = file_name_path + str('.aria2')
                os.system('rm "' + str(file_name_aria) +'"')
        else:
            self.statusbar.showMessage("Please select an item first!")
        self.selectedRow()

    def propertiesButtonPressed(self,button):
        selected_row_return = self.selectedRow()
        if selected_row_return != None :
            add_link_dictionary_str = self.download_table.item(selected_row_return , 9).text() 
            add_link_dictionary = ast.literal_eval(add_link_dictionary_str) 
            gid = self.download_table.item(selected_row_return , 8 ).text()
            propertieswindow = PropertiesWindow(self.propertiesCallback ,gid)
            self.propertieswindows_list.append(propertieswindow)
            self.propertieswindows_list[len(self.propertieswindows_list) - 1].show()

    def propertiesCallback(self,add_link_dictionary , gid ):
        download_info_file = download_info_folder + "/" + gid
        f = Open(download_info_file)
        download_info_file_lines = f.readlines()
        f.close()
        f = Open(download_info_file , "w")
        for i in range(10):
            if i == 9 :
                f.writelines(str(add_link_dictionary) + "\n")
            else:
                f.writelines(download_info_file_lines[i].strip() + "\n")

        f.close()
            
    def progressButtonPressed(self,button):
        selected_row_return = self.selectedRow()
        if selected_row_return != None:
            gid = self.download_table.item(selected_row_return , 8 ).text()
            member_number = self.progress_window_list_dict[gid]
            if self.progress_window_list[member_number].isVisible() == False:
                self.progress_window_list[member_number].show()
            else :
                self.progress_window_list[member_number].hide()

    def progressBarOpen(self,gid):
            progress_window = ProgressWindow(gid)
            self.progress_window_list.append(progress_window)
            member_number = len(self.progress_window_list) - 1
            self.progress_window_list_dict[gid] = member_number 
            self.progress_window_list[member_number].show()
 

#close event
    def closeEvent(self, event):
        self.hide()
        self.system_tray_icon.hide()
        download.shutDown()
        sleep(0.5)
        global shutdown_notification
        shutdown_notification = 1
        while shutdown_notification != 2:
            sleep (0.1)

        QCoreApplication.instance().closeAllWindows()
        for qthread in self.threadPool :
            try:
                qthread.exit(0)
                sleep(0.1)
                answer = qthread.isRunning()
                print(answer)
            except:
                print("not quit")


        QCoreApplication.instance().quit
        print("Persepolis Closed")

    def systemTrayPressed(self,click):
        if click == 3 :
            self.minMaxTray(click)
            

    def minMaxTray(self,menu):
        if self.isVisible() == False:
            self.minimizeAction.setText('Minimize to system tray')
            self.minimizeAction.setIcon(QIcon(icons + 'minimize'))
            self.show()

        else :
            self.hide()
            self.minimizeAction.setText('Show main Window')
            self.minimizeAction.setIcon(QIcon(icons + 'window'))

    def stopAllDownloads(self,menu):
        active_gids = []
        for i in range(self.download_table.rowCount()):
            try:
                row_status = self.download_table.item(i , 1).text()
                if row_status == 'downloading' or row_status == 'paused' or row_status == 'waiting':
                    row_gid = self.download_table.item(i , 8).text()
                    active_gids.append(row_gid)
            except :
                pass
        for gid in active_gids:
            answer = download.downloadStop(gid)
            if answer == 'None':
                notifySend("Aria2 did not respond!" , "Try agian!" , 10000 , 'critical' )


            sleep(0.3)

           

    def pauseAllDownloads(self,menu):
#get active gid of downloads from aria
        active_gids = download.activeDownloads()
#check that if gid is in download_list_file_active
        f = Open(download_list_file_active)
        download_list_file_active_lines = f.readlines()
        f.close()
        for i in range(len(download_list_file_active_lines)):
            download_list_file_active_lines[i] = download_list_file_active_lines[i].strip()

        for gid in active_gids :
            if gid in download_list_file_active_lines :
                answer = download.downloadPause(gid)
                if answer == 'None':
                    notifySend("Aria2 did not respond!" , "Try agian!" , 10000 , 'critical' )

                sleep(0.3)
            

    def openPreferences(self,menu):
        self.preferenceswindow = PreferencesWindow()
        self.preferenceswindow.show()


    def openAbout(self,menu):
        self.about_window = AboutWindow()
        self.about_window.show()
Beispiel #14
0
class TreeMainControl(QObject):
    """Class to handle all global controls.

    Provides methods for all controls and stores local control objects.
    """
    def __init__(self, pathObjects, parent=None):
        """Initialize the main tree controls

        Arguments:
            pathObjects -- a list of file objects to open
            parent -- the parent QObject if given
        """
        super().__init__(parent)
        self.localControls = []
        self.activeControl = None
        self.trayIcon = None
        self.isTrayMinimized = False
        self.configDialog = None
        self.sortDialog = None
        self.numberingDialog = None
        self.findTextDialog = None
        self.findConditionDialog = None
        self.findReplaceDialog = None
        self.filterTextDialog = None
        self.filterConditionDialog = None
        self.basicHelpView = None
        self.passwords = {}
        globalref.mainControl = self
        self.allActions = {}
        try:
            # check for existing TreeLine session
            socket = QLocalSocket()
            socket.connectToServer('treeline3-session', QIODevice.WriteOnly)
            # if found, send files to open and exit TreeLine
            if socket.waitForConnected(1000):
                socket.write(
                    bytes(repr([str(path) for path in pathObjects]), 'utf-8'))
                if socket.waitForBytesWritten(1000):
                    socket.close()
                    sys.exit(0)
            # start local server to listen for attempt to start new session
            self.serverSocket = QLocalServer()
            self.serverSocket.listen('treeline3-session')
            self.serverSocket.newConnection.connect(self.getSocket)
        except AttributeError:
            print(_('Warning:  Could not create local socket'))
        mainVersion = '.'.join(__version__.split('.')[:2])
        globalref.genOptions = options.Options('general', 'TreeLine',
                                               mainVersion, 'bellz')
        optiondefaults.setGenOptionDefaults(globalref.genOptions)
        globalref.miscOptions = options.Options('misc')
        optiondefaults.setMiscOptionDefaults(globalref.miscOptions)
        globalref.histOptions = options.Options('history')
        optiondefaults.setHistOptionDefaults(globalref.histOptions)
        globalref.toolbarOptions = options.Options('toolbar')
        optiondefaults.setToolbarOptionDefaults(globalref.toolbarOptions)
        globalref.keyboardOptions = options.Options('keyboard')
        optiondefaults.setKeyboardOptionDefaults(globalref.keyboardOptions)
        try:
            globalref.genOptions.readFile()
            globalref.miscOptions.readFile()
            globalref.histOptions.readFile()
            globalref.toolbarOptions.readFile()
            globalref.keyboardOptions.readFile()
        except IOError:
            errorDir = options.Options.basePath
            if not errorDir:
                errorDir = _('missing directory')
            QMessageBox.warning(
                None, 'TreeLine',
                _('Error - could not write config file to {}').format(
                    errorDir))
            options.Options.basePath = None
        iconPathList = self.findResourcePaths('icons', iconPath)
        globalref.toolIcons = icondict.IconDict(
            [path / 'toolbar' for path in iconPathList],
            ['', '32x32', '16x16'])
        globalref.toolIcons.loadAllIcons()
        windowIcon = globalref.toolIcons.getIcon('treelogo')
        if windowIcon:
            QApplication.setWindowIcon(windowIcon)
        globalref.treeIcons = icondict.IconDict(iconPathList, ['', 'tree'])
        icon = globalref.treeIcons.getIcon('default')
        qApp.setStyle(QStyleFactory.create('Fusion'))
        setThemeColors()
        self.recentFiles = recentfiles.RecentFileList()
        if globalref.genOptions['AutoFileOpen'] and not pathObjects:
            recentPath = self.recentFiles.firstPath()
            if recentPath:
                pathObjects = [recentPath]
        self.setupActions()
        self.systemFont = QApplication.font()
        self.updateAppFont()
        if globalref.genOptions['MinToSysTray']:
            self.createTrayIcon()
        qApp.focusChanged.connect(self.updateActionsAvail)
        if pathObjects:
            for pathObj in pathObjects:
                self.openFile(pathObj, True)
        else:
            self.createLocalControl()

    def getSocket(self):
        """Open a socket from an attempt to open a second Treeline instance.

        Opens the file (or raise and focus if open) in this instance.
        """
        socket = self.serverSocket.nextPendingConnection()
        if socket and socket.waitForReadyRead(1000):
            data = str(socket.readAll(), 'utf-8')
            try:
                paths = ast.literal_eval(data)
                if paths:
                    for path in paths:
                        self.openFile(pathlib.Path(path), True)
                else:
                    self.activeControl.activeWindow.activateAndRaise()
            except (SyntaxError, ValueError, TypeError):
                pass

    def findResourcePaths(self, resourceName, preferredPath=''):
        """Return list of potential non-empty pathlib objects for the resource.

        List includes preferred, module and user option paths.
        Arguments:
            resourceName -- the typical name of the resource directory
            preferredPath -- add this as the second path if given
        """
        modPath = pathlib.Path(sys.path[0]).resolve()
        if modPath.is_file():
            modPath = modPath.parent  # for frozen binary
        pathList = [modPath / '..' / resourceName, modPath / resourceName]
        if options.Options.basePath:
            basePath = pathlib.Path(options.Options.basePath)
            pathList.insert(0, basePath / resourceName)
        if preferredPath:
            pathList.insert(1, pathlib.Path(preferredPath))
        return [
            path.resolve() for path in pathList
            if path.is_dir() and list(path.iterdir())
        ]

    def findResourceFile(self, fileName, resourceName, preferredPath=''):
        """Return a path object for a resource file.

        Add a language code before the extension if it exists.
        Arguments:
            fileName -- the name of the file to find
            resourceName -- the typical name of the resource directory
            preferredPath -- search this path first if given
        """
        fileList = [fileName]
        if globalref.lang and globalref.lang != 'C':
            fileList[0:0] = [
                fileName.replace('.', '_{0}.'.format(globalref.lang)),
                fileName.replace('.', '_{0}.'.format(globalref.lang[:2]))
            ]
        for fileName in fileList:
            for path in self.findResourcePaths(resourceName, preferredPath):
                if (path / fileName).is_file():
                    return path / fileName
        return None

    def defaultPathObj(self, dirOnly=False):
        """Return a reasonable default file path object.

        Used for open, save-as, import and export.
        Arguments:
            dirOnly -- if True, do not include basename of file
        """
        pathObj = None
        if self.activeControl:
            pathObj = self.activeControl.filePathObj
        if not pathObj:
            pathObj = self.recentFiles.firstDir()
            if not pathObj:
                pathObj = pathlib.Path.home()
        if dirOnly:
            pathObj = pathObj.parent
        return pathObj

    def openFile(self,
                 pathObj,
                 forceNewWindow=False,
                 checkModified=False,
                 importOnFail=True):
        """Open the file given by path if not already open.

        If already open in a different window, focus and raise the window.
        Arguments:
            pathObj -- the path object to read
            forceNewWindow -- if True, use a new window regardless of option
            checkModified -- if True & not new win, prompt if file modified
            importOnFail -- if True, prompts for import on non-TreeLine files
        """
        match = [
            control for control in self.localControls
            if pathObj == control.filePathObj
        ]
        if match and self.activeControl not in match:
            control = match[0]
            control.activeWindow.activateAndRaise()
            self.updateLocalControlRef(control)
            return
        if checkModified and not (forceNewWindow
                                  or globalref.genOptions['OpenNewWindow']
                                  or self.activeControl.checkSaveChanges()):
            return
        if not self.checkAutoSave(pathObj):
            if not self.localControls:
                self.createLocalControl()
            return
        QApplication.setOverrideCursor(Qt.WaitCursor)
        try:
            self.createLocalControl(pathObj, None, forceNewWindow)
            self.recentFiles.addItem(pathObj)
            if not (globalref.genOptions['SaveTreeStates'] and
                    self.recentFiles.retrieveTreeState(self.activeControl)):
                self.activeControl.expandRootNodes()
                self.activeControl.selectRootSpot()
            QApplication.restoreOverrideCursor()
        except IOError:
            QApplication.restoreOverrideCursor()
            QMessageBox.warning(
                QApplication.activeWindow(), 'TreeLine',
                _('Error - could not read file {0}').format(pathObj))
            self.recentFiles.removeItem(pathObj)
        except (ValueError, KeyError, TypeError):
            fileObj = pathObj.open('rb')
            fileObj, encrypted = self.decryptFile(fileObj)
            if not fileObj:
                if not self.localControls:
                    self.createLocalControl()
                QApplication.restoreOverrideCursor()
                return
            fileObj, compressed = self.decompressFile(fileObj)
            if compressed or encrypted:
                try:
                    textFileObj = io.TextIOWrapper(fileObj, encoding='utf-8')
                    self.createLocalControl(textFileObj, None, forceNewWindow)
                    fileObj.close()
                    textFileObj.close()
                    self.recentFiles.addItem(pathObj)
                    if not (globalref.genOptions['SaveTreeStates']
                            and self.recentFiles.retrieveTreeState(
                                self.activeControl)):
                        self.activeControl.expandRootNodes()
                        self.activeControl.selectRootSpot()
                    self.activeControl.compressed = compressed
                    self.activeControl.encrypted = encrypted
                    QApplication.restoreOverrideCursor()
                    return
                except (ValueError, KeyError, TypeError):
                    pass
            fileObj.close()
            importControl = imports.ImportControl(pathObj)
            structure = importControl.importOldTreeLine()
            if structure:
                self.createLocalControl(pathObj, structure, forceNewWindow)
                self.activeControl.printData.readData(
                    importControl.treeLineRootAttrib)
                self.recentFiles.addItem(pathObj)
                self.activeControl.expandRootNodes()
                self.activeControl.imported = True
                QApplication.restoreOverrideCursor()
                return
            QApplication.restoreOverrideCursor()
            if importOnFail:
                importControl = imports.ImportControl(pathObj)
                structure = importControl.interactiveImport(True)
                if structure:
                    self.createLocalControl(pathObj, structure, forceNewWindow)
                    self.activeControl.imported = True
                    return
            else:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Error - invalid TreeLine file {0}').format(pathObj))
                self.recentFiles.removeItem(pathObj)
        if not self.localControls:
            self.createLocalControl()

    def decryptFile(self, fileObj):
        """Check for encryption and decrypt the fileObj if needed.

        Return a tuple of the file object and True if it was encrypted.
        Return None for the file object if the user cancels.
        Arguments:
            fileObj -- the file object to check and decrypt
        """
        if fileObj.read(len(encryptPrefix)) != encryptPrefix:
            fileObj.seek(0)
            return (fileObj, False)
        while True:
            pathObj = pathlib.Path(fileObj.name)
            password = self.passwords.get(pathObj, '')
            if not password:
                QApplication.restoreOverrideCursor()
                dialog = miscdialogs.PasswordDialog(
                    False, pathObj.name, QApplication.activeWindow())
                if dialog.exec_() != QDialog.Accepted:
                    fileObj.close()
                    return (None, True)
                QApplication.setOverrideCursor(Qt.WaitCursor)
                password = dialog.password
                if miscdialogs.PasswordDialog.remember:
                    self.passwords[pathObj] = password
            try:
                text = p3.p3_decrypt(fileObj.read(), password.encode())
                fileIO = io.BytesIO(text)
                fileIO.name = fileObj.name
                fileObj.close()
                return (fileIO, True)
            except p3.CryptError:
                try:
                    del self.passwords[pathObj]
                except KeyError:
                    pass

    def decompressFile(self, fileObj):
        """Check for compression and decompress the fileObj if needed.

        Return a tuple of the file object and True if it was compressed.
        Arguments:
            fileObj -- the file object to check and decompress
        """
        prefix = fileObj.read(2)
        fileObj.seek(0)
        if prefix != b'\037\213':
            return (fileObj, False)
        try:
            newFileObj = gzip.GzipFile(fileobj=fileObj)
        except zlib.error:
            return (fileObj, False)
        newFileObj.name = fileObj.name
        return (newFileObj, True)

    def checkAutoSave(self, pathObj):
        """Check for presence of auto save file & prompt user.

        Return True if OK to contimue, False if aborting or already loaded.
        Arguments:
            pathObj -- the base path object to search for a backup
        """
        if not globalref.genOptions['AutoSaveMinutes']:
            return True
        basePath = pathObj
        pathObj = pathlib.Path(str(pathObj) + '~')
        if not pathObj.is_file():
            return True
        msgBox = QMessageBox(
            QMessageBox.Information, 'TreeLine',
            _('Backup file "{}" exists.\nA previous '
              'session may have crashed').format(pathObj),
            QMessageBox.NoButton, QApplication.activeWindow())
        restoreButton = msgBox.addButton(_('&Restore Backup'),
                                         QMessageBox.ApplyRole)
        deleteButton = msgBox.addButton(_('&Delete Backup'),
                                        QMessageBox.DestructiveRole)
        cancelButton = msgBox.addButton(_('&Cancel File Open'),
                                        QMessageBox.RejectRole)
        msgBox.exec_()
        if msgBox.clickedButton() == restoreButton:
            self.openFile(pathObj)
            if self.activeControl.filePathObj != pathObj:
                return False
            try:
                basePath.unlink()
                pathObj.rename(basePath)
            except OSError:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Error - could not rename "{0}" to "{1}"').format(
                        pathObj, basePath))
                return False
            self.activeControl.filePathObj = basePath
            self.activeControl.updateWindowCaptions()
            self.recentFiles.removeItem(pathObj)
            self.recentFiles.addItem(basePath)
            return False
        elif msgBox.clickedButton() == deleteButton:
            try:
                pathObj.unlink()
            except OSError:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Error - could not remove backup file {}').format(
                        pathObj))
        else:  # cancel button
            return False
        return True

    def createLocalControl(self,
                           pathObj=None,
                           treeStruct=None,
                           forceNewWindow=False):
        """Create a new local control object and add it to the list.

        Use an imported structure if given or open the file if path is given.
        Arguments:
            pathObj -- the path object or file object for the control to open
            treeStruct -- the imported structure to use
            forceNewWindow -- if True, use a new window regardless of option
        """
        localControl = treelocalcontrol.TreeLocalControl(
            self.allActions, pathObj, treeStruct, forceNewWindow)
        localControl.controlActivated.connect(self.updateLocalControlRef)
        localControl.controlClosed.connect(self.removeLocalControlRef)
        self.localControls.append(localControl)
        self.updateLocalControlRef(localControl)
        localControl.updateRightViews()
        localControl.updateCommandsAvail()

    def updateLocalControlRef(self, localControl):
        """Set the given local control as active.

        Called by signal from a window becoming active.
        Also updates non-modal dialogs.
        Arguments:
            localControl -- the new active local control
        """
        if localControl != self.activeControl:
            self.activeControl = localControl
            if self.configDialog and self.configDialog.isVisible():
                self.configDialog.setRefs(self.activeControl)

    def removeLocalControlRef(self, localControl):
        """Remove ref to local control based on a closing signal.

        Also do application exit clean ups if last control closing.
        Arguments:
            localControl -- the local control that is closing
        """
        self.localControls.remove(localControl)
        if globalref.genOptions['SaveTreeStates']:
            self.recentFiles.saveTreeState(localControl)
        if not self.localControls:
            if globalref.genOptions['SaveWindowGeom']:
                localControl.windowList[0].saveWindowGeom()
            else:
                localControl.windowList[0].resetWindowGeom()
            self.recentFiles.writeItems()
            localControl.windowList[0].saveToolbarPosition()
            globalref.histOptions.writeFile()
            if self.trayIcon:
                self.trayIcon.hide()
        localControl.deleteLater()

    def createTrayIcon(self):
        """Create a new system tray icon if not already created.
        """
        if QSystemTrayIcon.isSystemTrayAvailable:
            if not self.trayIcon:
                self.trayIcon = QSystemTrayIcon(qApp.windowIcon(), qApp)
                self.trayIcon.activated.connect(self.toggleTrayShow)
            self.trayIcon.show()

    def trayMinimize(self):
        """Minimize to tray based on window minimize signal.
        """
        if self.trayIcon and QSystemTrayIcon.isSystemTrayAvailable:
            # skip minimize to tray if not all windows minimized
            for control in self.localControls:
                for window in control.windowList:
                    if not window.isMinimized():
                        return
            for control in self.localControls:
                for window in control.windowList:
                    window.hide()
            self.isTrayMinimized = True

    def toggleTrayShow(self):
        """Toggle show and hide application based on system tray icon click.
        """
        if self.isTrayMinimized:
            for control in self.localControls:
                for window in control.windowList:
                    window.show()
                    window.showNormal()
            self.activeControl.activeWindow.treeView.setFocus()
        else:
            for control in self.localControls:
                for window in control.windowList:
                    window.hide()
        self.isTrayMinimized = not self.isTrayMinimized

    def updateConfigDialog(self):
        """Update the config dialog for changes if it exists.
        """
        if self.configDialog:
            self.configDialog.reset()

    def currentStatusBar(self):
        """Return the status bar from the current main window.
        """
        return self.activeControl.activeWindow.statusBar()

    def windowActions(self):
        """Return a list of window menu actions from each local control.
        """
        actions = []
        for control in self.localControls:
            actions.extend(
                control.windowActions(
                    len(actions) + 1, control == self.activeControl))
        return actions

    def updateActionsAvail(self, oldWidget, newWidget):
        """Update command availability based on focus changes.

        Arguments:
            oldWidget -- the previously focused widget
            newWidget -- the newly focused widget
        """
        self.allActions['FormatSelectAll'].setEnabled(
            hasattr(newWidget, 'selectAll')
            and not hasattr(newWidget, 'editTriggers'))

    def setupActions(self):
        """Add the actions for contols at the global level.
        """
        fileNewAct = QAction(_('&New...'),
                             self,
                             toolTip=_('New File'),
                             statusTip=_('Start a new file'))
        fileNewAct.triggered.connect(self.fileNew)
        self.allActions['FileNew'] = fileNewAct

        fileOpenAct = QAction(_('&Open...'),
                              self,
                              toolTip=_('Open File'),
                              statusTip=_('Open a file from disk'))
        fileOpenAct.triggered.connect(self.fileOpen)
        self.allActions['FileOpen'] = fileOpenAct

        fileSampleAct = QAction(_('Open Sa&mple...'),
                                self,
                                toolTip=_('Open Sample'),
                                statusTip=_('Open a sample file'))
        fileSampleAct.triggered.connect(self.fileOpenSample)
        self.allActions['FileOpenSample'] = fileSampleAct

        fileImportAct = QAction(_('&Import...'),
                                self,
                                statusTip=_('Open a non-TreeLine file'))
        fileImportAct.triggered.connect(self.fileImport)
        self.allActions['FileImport'] = fileImportAct

        fileQuitAct = QAction(_('&Quit'),
                              self,
                              statusTip=_('Exit the application'))
        fileQuitAct.triggered.connect(self.fileQuit)
        self.allActions['FileQuit'] = fileQuitAct

        dataConfigAct = QAction(
            _('&Configure Data Types...'),
            self,
            statusTip=_('Modify data types, fields & output lines'),
            checkable=True)
        dataConfigAct.triggered.connect(self.dataConfigDialog)
        self.allActions['DataConfigType'] = dataConfigAct

        dataVisualConfigAct = QAction(
            _('Show C&onfiguration Structure...'),
            self,
            statusTip=_('Show read-only visualization of type structure'))
        dataVisualConfigAct.triggered.connect(self.dataVisualConfig)
        self.allActions['DataVisualConfig'] = dataVisualConfigAct

        dataSortAct = QAction(_('Sor&t Nodes...'),
                              self,
                              statusTip=_('Define node sort operations'),
                              checkable=True)
        dataSortAct.triggered.connect(self.dataSortDialog)
        self.allActions['DataSortNodes'] = dataSortAct

        dataNumberingAct = QAction(_('Update &Numbering...'),
                                   self,
                                   statusTip=_('Update node numbering fields'),
                                   checkable=True)
        dataNumberingAct.triggered.connect(self.dataNumberingDialog)
        self.allActions['DataNumbering'] = dataNumberingAct

        toolsFindTextAct = QAction(
            _('&Find Text...'),
            self,
            statusTip=_('Find text in node titles & data'),
            checkable=True)
        toolsFindTextAct.triggered.connect(self.toolsFindTextDialog)
        self.allActions['ToolsFindText'] = toolsFindTextAct

        toolsFindConditionAct = QAction(
            _('&Conditional Find...'),
            self,
            statusTip=_('Use field conditions to find nodes'),
            checkable=True)
        toolsFindConditionAct.triggered.connect(self.toolsFindConditionDialog)
        self.allActions['ToolsFindCondition'] = toolsFindConditionAct

        toolsFindReplaceAct = QAction(
            _('Find and &Replace...'),
            self,
            statusTip=_('Replace text strings in node data'),
            checkable=True)
        toolsFindReplaceAct.triggered.connect(self.toolsFindReplaceDialog)
        self.allActions['ToolsFindReplace'] = toolsFindReplaceAct

        toolsFilterTextAct = QAction(
            _('&Text Filter...'),
            self,
            statusTip=_('Filter nodes to only show text matches'),
            checkable=True)
        toolsFilterTextAct.triggered.connect(self.toolsFilterTextDialog)
        self.allActions['ToolsFilterText'] = toolsFilterTextAct

        toolsFilterConditionAct = QAction(
            _('C&onditional Filter...'),
            self,
            statusTip=_('Use field conditions to filter nodes'),
            checkable=True)
        toolsFilterConditionAct.triggered.connect(
            self.toolsFilterConditionDialog)
        self.allActions['ToolsFilterCondition'] = toolsFilterConditionAct

        toolsGenOptionsAct = QAction(
            _('&General Options...'),
            self,
            statusTip=_('Set user preferences for all files'))
        toolsGenOptionsAct.triggered.connect(self.toolsGenOptions)
        self.allActions['ToolsGenOptions'] = toolsGenOptionsAct

        toolsShortcutAct = QAction(_('Set &Keyboard Shortcuts...'),
                                   self,
                                   statusTip=_('Customize keyboard commands'))
        toolsShortcutAct.triggered.connect(self.toolsCustomShortcuts)
        self.allActions['ToolsShortcuts'] = toolsShortcutAct

        toolsToolbarAct = QAction(_('C&ustomize Toolbars...'),
                                  self,
                                  statusTip=_('Customize toolbar buttons'))
        toolsToolbarAct.triggered.connect(self.toolsCustomToolbars)
        self.allActions['ToolsToolbars'] = toolsToolbarAct

        toolsFontsAct = QAction(
            _('Customize Fo&nts...'),
            self,
            statusTip=_('Customize fonts in various views'))
        toolsFontsAct.triggered.connect(self.toolsCustomFonts)
        self.allActions['ToolsFonts'] = toolsFontsAct

        helpBasicAct = QAction(_('&Basic Usage...'),
                               self,
                               statusTip=_('Display basic usage instructions'))
        helpBasicAct.triggered.connect(self.helpViewBasic)
        self.allActions['HelpBasic'] = helpBasicAct

        helpFullAct = QAction(
            _('&Full Documentation...'),
            self,
            statusTip=_('Open a TreeLine file with full documentation'))
        helpFullAct.triggered.connect(self.helpViewFull)
        self.allActions['HelpFull'] = helpFullAct

        helpAboutAct = QAction(
            _('&About TreeLine...'),
            self,
            statusTip=_('Display version info about this program'))
        helpAboutAct.triggered.connect(self.helpAbout)
        self.allActions['HelpAbout'] = helpAboutAct

        formatSelectAllAct = QAction(
            _('&Select All'),
            self,
            statusTip=_('Select all text in an editor'))
        formatSelectAllAct.setEnabled(False)
        formatSelectAllAct.triggered.connect(self.formatSelectAll)
        self.allActions['FormatSelectAll'] = formatSelectAllAct

        helpAboutAct = QAction(
            _('&About TreeLine...'),
            self,
            statusTip=_('Display version info about this program'))
        helpAboutAct.triggered.connect(self.helpAbout)
        self.allActions['HelpAbout'] = helpAboutAct

        for name, action in self.allActions.items():
            icon = globalref.toolIcons.getIcon(name.lower())
            if icon:
                action.setIcon(icon)
            key = globalref.keyboardOptions[name]
            if not key.isEmpty():
                action.setShortcut(key)

    def fileNew(self):
        """Start a new blank file.
        """
        if (globalref.genOptions['OpenNewWindow']
                or self.activeControl.checkSaveChanges()):
            searchPaths = self.findResourcePaths('templates', templatePath)
            if searchPaths:
                dialog = miscdialogs.TemplateFileDialog(
                    _('New File'), _('&Select Template'), searchPaths)
                if dialog.exec_() == QDialog.Accepted:
                    self.createLocalControl(dialog.selectedPath())
                    self.activeControl.filePathObj = None
                    self.activeControl.updateWindowCaptions()
                    self.activeControl.expandRootNodes()
            else:
                self.createLocalControl()
            self.activeControl.selectRootSpot()

    def fileOpen(self):
        """Prompt for a filename and open it.
        """
        if (globalref.genOptions['OpenNewWindow']
                or self.activeControl.checkSaveChanges()):
            filters = ';;'.join((globalref.fileFilters['trlnopen'],
                                 globalref.fileFilters['all']))
            fileName, selFilter = QFileDialog.getOpenFileName(
                QApplication.activeWindow(), _('TreeLine - Open File'),
                str(self.defaultPathObj(True)), filters)
            if fileName:
                self.openFile(pathlib.Path(fileName))

    def fileOpenSample(self):
        """Open a sample file from the doc directories.
        """
        if (globalref.genOptions['OpenNewWindow']
                or self.activeControl.checkSaveChanges()):
            searchPaths = self.findResourcePaths('samples', samplePath)
            dialog = miscdialogs.TemplateFileDialog(_('Open Sample File'),
                                                    _('&Select Sample'),
                                                    searchPaths, False)
            if dialog.exec_() == QDialog.Accepted:
                self.createLocalControl(dialog.selectedPath())
                name = dialog.selectedName() + '.trln'
                self.activeControl.filePathObj = pathlib.Path(name)
                self.activeControl.updateWindowCaptions()
                self.activeControl.expandRootNodes()
                self.activeControl.imported = True

    def fileImport(self):
        """Prompt for an import type, then a file to import.
        """
        importControl = imports.ImportControl()
        structure = importControl.interactiveImport()
        if structure:
            self.createLocalControl(importControl.pathObj, structure)
            if importControl.treeLineRootAttrib:
                self.activeControl.printData.readData(
                    importControl.treeLineRootAttrib)
            self.activeControl.imported = True

    def fileQuit(self):
        """Close all windows to exit the applications.
        """
        for control in self.localControls[:]:
            control.closeWindows()

    def dataConfigDialog(self, show):
        """Show or hide the non-modal data config dialog.

        Arguments:
            show -- true if dialog should be shown, false to hide it
        """
        if show:
            if not self.configDialog:
                self.configDialog = configdialog.ConfigDialog()
                dataConfigAct = self.allActions['DataConfigType']
                self.configDialog.dialogShown.connect(dataConfigAct.setChecked)
            self.configDialog.setRefs(self.activeControl, True)
            self.configDialog.show()
        else:
            self.configDialog.close()

    def dataVisualConfig(self):
        """Show a TreeLine file to visualize the config structure.
        """
        structure = (
            self.activeControl.structure.treeFormats.visualConfigStructure(
                str(self.activeControl.filePathObj)))
        self.createLocalControl(treeStruct=structure, forceNewWindow=True)
        self.activeControl.filePathObj = pathlib.Path('structure.trln')
        self.activeControl.updateWindowCaptions()
        self.activeControl.expandRootNodes()
        self.activeControl.imported = True
        win = self.activeControl.activeWindow
        win.rightTabs.setCurrentWidget(win.outputSplitter)

    def dataSortDialog(self, show):
        """Show or hide the non-modal data sort nodes dialog.

        Arguments:
            show -- true if dialog should be shown, false to hide it
        """
        if show:
            if not self.sortDialog:
                self.sortDialog = miscdialogs.SortDialog()
                dataSortAct = self.allActions['DataSortNodes']
                self.sortDialog.dialogShown.connect(dataSortAct.setChecked)
            self.sortDialog.show()
        else:
            self.sortDialog.close()

    def dataNumberingDialog(self, show):
        """Show or hide the non-modal update node numbering dialog.

        Arguments:
            show -- true if dialog should be shown, false to hide it
        """
        if show:
            if not self.numberingDialog:
                self.numberingDialog = miscdialogs.NumberingDialog()
                dataNumberingAct = self.allActions['DataNumbering']
                self.numberingDialog.dialogShown.connect(
                    dataNumberingAct.setChecked)
            self.numberingDialog.show()
            if not self.numberingDialog.checkForNumberingFields():
                self.numberingDialog.close()
        else:
            self.numberingDialog.close()

    def toolsFindTextDialog(self, show):
        """Show or hide the non-modal find text dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.findTextDialog:
                self.findTextDialog = miscdialogs.FindFilterDialog()
                toolsFindTextAct = self.allActions['ToolsFindText']
                self.findTextDialog.dialogShown.connect(
                    toolsFindTextAct.setChecked)
            self.findTextDialog.selectAllText()
            self.findTextDialog.show()
        else:
            self.findTextDialog.close()

    def toolsFindConditionDialog(self, show):
        """Show or hide the non-modal conditional find dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.findConditionDialog:
                dialogType = conditional.FindDialogType.findDialog
                self.findConditionDialog = (conditional.ConditionDialog(
                    dialogType, _('Conditional Find')))
                toolsFindConditionAct = self.allActions['ToolsFindCondition']
                (self.findConditionDialog.dialogShown.connect(
                    toolsFindConditionAct.setChecked))
            else:
                self.findConditionDialog.loadTypeNames()
            self.findConditionDialog.show()
        else:
            self.findConditionDialog.close()

    def toolsFindReplaceDialog(self, show):
        """Show or hide the non-modal find and replace text dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.findReplaceDialog:
                self.findReplaceDialog = miscdialogs.FindReplaceDialog()
                toolsFindReplaceAct = self.allActions['ToolsFindReplace']
                self.findReplaceDialog.dialogShown.connect(
                    toolsFindReplaceAct.setChecked)
            else:
                self.findReplaceDialog.loadTypeNames()
            self.findReplaceDialog.show()
        else:
            self.findReplaceDialog.close()

    def toolsFilterTextDialog(self, show):
        """Show or hide the non-modal filter text dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.filterTextDialog:
                self.filterTextDialog = miscdialogs.FindFilterDialog(True)
                toolsFilterTextAct = self.allActions['ToolsFilterText']
                self.filterTextDialog.dialogShown.connect(
                    toolsFilterTextAct.setChecked)
            self.filterTextDialog.selectAllText()
            self.filterTextDialog.show()
        else:
            self.filterTextDialog.close()

    def toolsFilterConditionDialog(self, show):
        """Show or hide the non-modal conditional filter dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.filterConditionDialog:
                dialogType = conditional.FindDialogType.filterDialog
                self.filterConditionDialog = (conditional.ConditionDialog(
                    dialogType, _('Conditional Filter')))
                toolsFilterConditionAct = (
                    self.allActions['ToolsFilterCondition'])
                (self.filterConditionDialog.dialogShown.connect(
                    toolsFilterConditionAct.setChecked))
            else:
                self.filterConditionDialog.loadTypeNames()
            self.filterConditionDialog.show()
        else:
            self.filterConditionDialog.close()

    def toolsGenOptions(self):
        """Set general user preferences for all files.
        """
        oldAutoSaveMinutes = globalref.genOptions['AutoSaveMinutes']
        oldColorTheme = globalref.genOptions['ColorTheme']
        dialog = options.OptionDialog(globalref.genOptions,
                                      QApplication.activeWindow())
        dialog.setWindowTitle(_('General Options'))
        if (dialog.exec_() == QDialog.Accepted
                and globalref.genOptions.modified):
            globalref.genOptions.writeFile()
            self.recentFiles.updateOptions()
            if globalref.genOptions['MinToSysTray']:
                self.createTrayIcon()
            elif self.trayIcon:
                self.trayIcon.hide()
            autoSaveMinutes = globalref.genOptions['AutoSaveMinutes']
            for control in self.localControls:
                for window in control.windowList:
                    window.updateWinGenOptions()
                control.structure.undoList.setNumLevels()
                control.updateAll(False)
                if autoSaveMinutes != oldAutoSaveMinutes:
                    control.resetAutoSave()
            if globalref.genOptions['ColorTheme'] != oldColorTheme:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Application must be restarted for '
                      'color theme changes to take effect'))

    def toolsCustomShortcuts(self):
        """Show dialog to customize keyboard commands.
        """
        actions = self.activeControl.activeWindow.allActions
        dialog = miscdialogs.CustomShortcutsDialog(actions,
                                                   QApplication.activeWindow())
        dialog.exec_()

    def toolsCustomToolbars(self):
        """Show dialog to customize toolbar buttons.
        """
        actions = self.activeControl.activeWindow.allActions
        dialog = miscdialogs.CustomToolbarDialog(actions, self.updateToolbars,
                                                 QApplication.activeWindow())
        dialog.exec_()

    def updateToolbars(self):
        """Update toolbars after changes in custom toolbar dialog.
        """
        for control in self.localControls:
            for window in control.windowList:
                window.setupToolbars()

    def toolsCustomFonts(self):
        """Show dialog to customize fonts in various views.
        """
        dialog = miscdialogs.CustomFontDialog(QApplication.activeWindow())
        dialog.updateRequired.connect(self.updateCustomFonts)
        dialog.exec_()

    def updateCustomFonts(self):
        """Update fonts in all windows based on a dialog signal.
        """
        self.updateAppFont()
        for control in self.localControls:
            for window in control.windowList:
                window.updateFonts()
            control.printData.setDefaultFont()
        for control in self.localControls:
            control.updateAll(False)

    def updateAppFont(self):
        """Update application default font from settings.
        """
        appFont = QFont(self.systemFont)
        appFontName = globalref.miscOptions['AppFont']
        if appFontName:
            appFont.fromString(appFontName)
        QApplication.setFont(appFont)

    def formatSelectAll(self):
        """Select all text in any currently focused editor.
        """
        try:
            QApplication.focusWidget().selectAll()
        except AttributeError:
            pass

    def helpViewBasic(self):
        """Display basic usage instructions.
        """
        if not self.basicHelpView:
            path = self.findResourceFile('basichelp.html', 'doc', docPath)
            if not path:
                QMessageBox.warning(QApplication.activeWindow(), 'TreeLine',
                                    _('Error - basic help file not found'))
                return
            self.basicHelpView = helpview.HelpView(path,
                                                   _('TreeLine Basic Usage'),
                                                   globalref.toolIcons)
        self.basicHelpView.show()

    def helpViewFull(self):
        """Open a TreeLine file with full documentation.
        """
        path = self.findResourceFile('documentation.trln', 'doc', docPath)
        if not path:
            QMessageBox.warning(QApplication.activeWindow(), 'TreeLine',
                                _('Error - documentation file not found'))
            return
        self.createLocalControl(path, forceNewWindow=True)
        self.activeControl.filePathObj = pathlib.Path('documentation.trln')
        self.activeControl.updateWindowCaptions()
        self.activeControl.expandRootNodes()
        self.activeControl.imported = True
        win = self.activeControl.activeWindow
        win.rightTabs.setCurrentWidget(win.outputSplitter)

    def helpAbout(self):
        """ Display version info about this program.
        """
        pyVersion = '.'.join([repr(num) for num in sys.version_info[:3]])
        textLines = [
            _('TreeLine version {0}').format(__version__),
            _('written by {0}').format(__author__), '',
            _('Library versions:'), '   Python:  {0}'.format(pyVersion),
            '   Qt:  {0}'.format(qVersion()),
            '   PyQt:  {0}'.format(PYQT_VERSION_STR),
            '   OS:  {0}'.format(platform.platform())
        ]
        dialog = miscdialogs.AboutDialog('TreeLine', textLines,
                                         QApplication.windowIcon(),
                                         QApplication.activeWindow())
        dialog.exec_()
Beispiel #15
0
class Ui_MainWindow(QMainWindow, object):
    def setupUi(self, MainWindow):
        #载入资源文件
        #载入字体
        for i in font_path:
            if exists(settings["workingDir"]+font_path[i]):
                QtGui.QFontDatabase.addApplicationFont(settings["workingDir"]+font_path[i])
            else:
                log("字体不存在:" + font_path[i] + "(" + settings["lang"] + ")", level="error")
                font_path[i] = "Arial"

        self.setObjectName("MainWindow")
        self.resize(400, 300)
        self.setFixedSize(MainWindow.width(), MainWindow.height())
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setWindowOpacity(settings["WindowOpacity"])
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint|QtCore.Qt.WindowStaysOnTopHint)

        #widget
        if exists(settings["workingDir"]+settings["backgroundPath"]):
            backgroundPath=settings["workingDir"]+settings["backgroundPath"]
            log("加载背景图片:" + backgroundPath)
        else:
            backgroundPath = ":/black.png"
            log("未找到背景图片")
        self.RoundWidget = QWidget(parent=MainWindow)
        self.RoundWidget.setGeometry(MainWindow.geometry())
        self.RoundWidget.setStyleSheet("QWidget{border-image: url("+backgroundPath.replace(sep,"/")+r") 100% 100% round;border-radius:15px;}")
        self.RoundWidget.show()

        #拖动功能
        self._startPos,self._endPos=QPoint(0,0),QPoint(0,0)

        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        QApplication.setQuitOnLastWindowClosed(False)

        self.BtnSearch = QtWidgets.QPushButton(self.centralwidget)
        self.BtnSearch.setGeometry(QtCore.QRect(350, 10, 40, 40))
        self.BtnSearch.setText("")
        self.BtnSearch.setIcon(QIcon(":/AuraProII.ico"))
        self.BtnSearch.setIconSize(QtCore.QSize(40, 40))
        self.BtnSearch.setStyleSheet(r"QPushButton{border:1px solid;padding:5px;border-radius:10px;}")
        self.BtnSearch.setObjectName("BtnSearch")
        QShortcut(QKeySequence(settings["SearchShortCut"]), self.BtnSearch).activated.connect(self.BtnSearchClickEvent)
        self.BtnSearch.setToolTip("快捷键:"+settings["SearchShortCut"])
        self.BtnSearch.clicked.connect(self.EndSearchEvent)
        self.BtnSearch.clicked.connect(self.BtnSearchClickEvent)

        self.StatusBar = QtWidgets.QStatusBar(self.centralwidget)
        self.StatusBar.setStyleSheet("QStatusBar{color:rgba"+settings["clStatusBar"]+";}")
        self.setStatusBar(self.StatusBar)
        self.StatusBar.showMessage("增强型奥拉 II:"+version)

        self.EdtName = TEdtName(self.centralwidget)
        self.EdtName.setGeometry(QtCore.QRect(10, 10, 340, 40))
        self.EdtName.setPlaceholderText("在这里输入名字...")
        self.EdtName.setStyleSheet("""
            TEdtName{
                border:1px grey;
                border-style: none;
                border-radius:10px;
                padding:1px 2px;
                color:rgb(255,0,0);
                background-color:rgba(0,0,0,127)
            }
            """)
        self.EdtName.show()

        self.TrayMenu = QMenu(self)
        self.TrayMenu.addAction(QAction("显示/隐藏主界面", parent=self.TrayMenu,checkable=True,checked=True, triggered=self.ToggleShowHide))
        self.TrayMenu.addAction(QAction("主界面置顶", parent=self.TrayMenu, checkable=True, checked=True, triggered=self.ToggleStayOnTop))
        self.TrayMenu.addSeparator()
        self.TrayMenu.addAction(QAction("关于...", parent=self.TrayMenu, triggered=self.ShowAbout))
        self.TrayMenu.addSeparator()
        self.TrayMenu.addAction(QAction("停用 增强型奥拉II",parent=self.TrayMenu, triggered=self.quit))

        self.TrayIcon = QSystemTrayIcon(self)
        self.TrayIcon.setIcon(QIcon(":/AuraProII.ico"))
        self.TrayIcon.setToolTip(u'增强型奥拉 II:激活中')
        self.TrayIcon.show()
        self.TrayIcon.showMessage(u"增强型奥拉 II:"+version, "Fly safe o/", 0)
        self.TrayIcon.activated.connect(self.TrayIconClickEvent)

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(lambda:self.TrayMenu.exec_(QCursor.pos()))
        self.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        #窗体创建完毕
        #信息列表
        self.MsgEntryList = {}
        # MsgEntryList结构
        # 每项为每个过程加入的信息
        # 如,StartSearchKB后,MsgEntryList中的内容应为:
        # {"SearchName":{
        #                   "Label":"正在搜索角色",
        #                   "ID":*******
        #               }
        #  "SearchKB":{...}
        # RefreshLabelList只会显示被SerializeMsgEntryList抽出的TMsgLabel对象

        #用于显示信息列表的Label列表
        self.LabelList = []
        self.LabelList_buffer = []
        self.LabelList_click_no = -1
        self.LabelList_pos=0

        #窗体创建完毕
        log("窗体创建完毕")

    def retranslateUi(self, MainWindow):
        _t = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_t("MainWindow", "MainWindow"))
        self.BtnSearch.setShortcut(_t("MainWindow", "Enter"))

    def quit(self):
        """
        终止程序。
        """
        self.TrayIcon.hide()
        self.closeEvent(None)
        self.EndSearchEvent()
        log("退出")
        QApplication.quit()

    def ToggleShowHide(self):
        """
        显示/隐藏主界面。
        """
        if self.sender().isChecked():
            self.show()
        else:
            self.hide()

    def ToggleStayOnTop(self):
        """
        切换窗口置顶。
        """
        if self.sender().isChecked():
            self.setWindowFlags(Qt.FramelessWindowHint|Qt.WindowStaysOnTopHint)
        else:
            self.setWindowFlags(Qt.FramelessWindowHint)

    def ShowAbout(self):
        msgBox = QMessageBox()
        msgBox.setWindowTitle(u'关于...')
        msgBox.setIconPixmap(QPixmap(":/AuraProII.png"))
        msgBox.setText(u"""
            增强型奥拉 II
            """+version+"""
            by 百万光年
            [email protected]""")
        msgBox.setWindowIcon(QIcon(":/AuraProII.ico"))

        msgBox.exec_()

    #event
    def mouseMoveEvent(self, e):  # 重写移动事件
        try:
            self.on_events=True
            self._endPos = e.pos() - self._startPos
            self.move(self.pos() + self._endPos)
        except Exception as error:
            self._endPos = e.pos()
            self._startPos = e.pos()
        finally:
            self.on_events = False

    def mousePressEvent(self, e):
        self.EdtName.clearFocus()
        if e.button() == Qt.LeftButton:
            self._isTracking = True
            self._startPos = QPoint(e.x(), e.y())

    def mouseReleaseEvent(self, e):
        if e.button() == Qt.LeftButton:
            self._isTracking = False
            self._startPos = None
            self._endPos = None

    def closeEvent(self, event):
        self.hide()

    def TrayIconClickEvent(self, reason):
        if reason == 1:
            self.TrayMenu.exec_(QCursor.pos())
        elif reason == 2:
            self.TrayMenu.actions()[0].setChecked(True)
            self.show()

    def wheelEvent(self, event):
        if len(self.LabelList)>0:
            self.on_events = True
            ori=event.angleDelta().y()/120 if not isinstance(event,int) else event
            if ori > 0:
                #向上移
                if self.LabelList[0].geometry().top() < self.EdtName.geometry().height()+self.EdtName.geometry().top():
                    self.LabelList_pos+=1
                    for i in self.LabelList:
                        i.move(i.geometry().left(),i.geometry().top() + 10)
            else:
                #向下移
                if self.LabelList[-1].geometry().top() + self.LabelList[-1].geometry().height() > self.StatusBar.geometry().top():
                    self.LabelList_pos-=1
                    for i in self.LabelList:
                        i.move(i.geometry().left(),i.geometry().top() - 10)
            if not isinstance(event, int):
                event.accept()
            self.on_events = False

    def BtnSearchClickEvent(self):
        return self.StartSearchName()

    def EndSearchEvent(self,e=None):
        """
        搜索结束事件。
        将传递信息的线程传来的
        """
        if isinstance(self.sender(), TThread):
            log("EndSearch:"+str(self.sender().__name__))
            MutEndSearch.lock()
            Msg = self.sender().Msg
            log("Msg="+str(Msg))

            if id(self.sender()) not in current_thread_set:
                #说明不是此次搜索的返回线程,直接丢弃
                MutEndSearch.unlock()
                return - 1

            #由SearchName返回
            if "getKMList" in Msg:#完成了一轮完整的搜索流程
                self.MsgEntryList = {}
                self.MsgEntryList.update(Msg)
                self.statusBar().showMessage("搜索完成")
                self.EdtName.setStyleSheet("""
                    TEdtName{
                        border: 2px groove white;
                        border-radius: 3px;
                        padding:2px 3px;
                        color:rgb(255,0,0);
                        background-color:rgba(0,0,0,0)
                    }
                    TEdtName:focus{
                        color:rgb(0,255,0);
                        background-color: rgb(0,0,0)
                    } """)
            elif "Error" in Msg:  #SearchName返回错误
                self.MsgEntryList = {}
                self.MsgEntryList.update(Msg)
                if Msg["Error"] == "getKMListError":
                    self.MsgEntryList.update({"ErrorLabel": TMsgEntry("获取KM列表失败",style_str=MDStyleStr(color=settings["clFailed"],font_size=settings["labelFontSize"]))})
                elif Msg["Error"] == "zkbError":
                    self.MsgEntryList.update({"ErrorLabel": TMsgEntry("zkb查询失败",style_str=MDStyleStr(color=settings["clFailed"],font_size=settings["labelFontSize"]))})
                elif Msg["Error"] == "esiError":
                    self.MsgEntryList.update({"ErrorLabel": TMsgEntry("查询角色ID失败", style_str=MDStyleStr(color=settings["clFailed"], font_size=settings["labelFontSize"]))})
                elif Msg["Error"] == "SearchKMError":
                    self.MsgEntryList.update({"ErrorLabel": TMsgEntry("查询KM失败", style_str=MDStyleStr(color=settings["clFailed"], font_size=settings["labelFontSize"]))})
                elif Msg["Error"] == "NoSuchCharacterError":
                    self.MsgEntryList.update({"ErrorLabel": TMsgEntry("无此角色", style_str=MDStyleStr(color=settings["clFailed"], font_size=settings["labelFontSize"]))})
                elif Msg["Error"] == "PlayerNoPVPData":
                    self.MsgEntryList.update({"ErrorLabel": TMsgEntry("没有该角色的统计数据",style_str=MDStyleStr(color=settings["clHint"],font_size=settings["labelFontSize"]))})

            elif "NameList" in Msg:  #多个搜索结果命中
                self.MsgEntryList.update({"MultipleHits": TMsgEntry("命中" + str(len(Msg["NameList"])) + "条搜索结果...",style_str=MDStyleStr(color=settings["clHint"],font_size=settings["labelFontSize"]))})
                NameList = Msg["NameList"][:]
                no=0
                for c in NameList:
                    no+=1
                    self.MultiThreadRun(func=addName, args=(c, no))
            elif "TooManyResults" in Msg:  #搜索结果命中数超过ResultCountLimit
                self.MsgEntryList.update(Msg)
                self.MultiThreadRun(func=SearchName,args=(Msg["name"],-1,True))

            #由addName返回
            elif "addName" in Msg:
                #处理addName返回的情况
                #addName会多并发调用EndSearchEvent,因此需要QMutex
                #addName会返回成对的name和characterID,每个返回都应被添加至self.MsgEntryList["addName"]
                if "addName" not in self.MsgEntryList:
                    self.MsgEntryList["addName"]=[]
                self.MsgEntryList["addName"] += Msg["addName"]

            #由SearchKM返回
            elif "SearchKM" in Msg:
                if self.LabelList_click_no != -1:
                    #需要找到被单击的Label在self.MsgEntryList中的位置
                    for i in range(len(self.MsgEntryList["getKMList"])):
                        #如果getKMList中记录的killmail_id==LabelList中记录的killmail_id
                        if (self.MsgEntryList["getKMList"][i][0][0]==self.LabelList[self.LabelList_click_no].MsgEntry.ClickArgs[1]):
                            self.MsgEntryList["getKMList"][i][2]=Msg
                            break
                self.statusBar().showMessage("KM已获取")
            MutEndSearch.unlock()
            self.RefreshLabelList()

    #custom
    def MultiThreadRun(self, *args, **kwargs):
        """
        启动多线程,用于网络IO
        """
        global thread_pool,current_thread_set
        t = TThread(*args, **kwargs)
        if not isinstance(t, TThread):
            return -1
        t.finished.connect(self.EndSearchEvent)
        if id(t) not in thread_pool:
            thread_pool.add(t)
            current_thread_set.add(id(t))
        else:
            QMessageBox.warning(self, "错误", "无法创建线程。", QMessageBox.standardButton)
            self.quit()
        return t.start()

    def RefreshLabelList(self, Msg=None):
        """
        刷新LabelList显示。
        Msg(None or dict):作为回调时需要在self.MsgEntryList中添加的信息
        """
        #作为多线程的回调,此进程需要加锁。
        global MutLabelList
        while not MutLabelList.tryLock(1):
            sleep(0.1)
            self.statusBar().showMessage("错误:无法刷新信息列表")
        ret=0

        #由于MsgLabel可能正在响应事件所以不能直接deleteLater
        #使用double buffer
        for i in self.LabelList_buffer:
            while (i.on_events):
                i.on_events=False
                sleep(0.1)
            i.deleteLater()
        for i in self.LabelList:
            i.hide()
        self.LabelList_buffer=self.LabelList[:]
        self.LabelList = []

        if isinstance(Msg,dict):
            self.MsgEntryList.update(Msg)
        #把MsgEntryList展平
        new_label_list = Serialize(self.MsgEntryList)

        count=-1
        for i in new_label_list:
            #每个i都是一个TMsgEntry
            count += 1
            if i.enable:
                self.LabelList.append(TMsgLabel(m=i,no=count))
        for i in self.LabelList:
            i.setParent(self.centralwidget)
            i.setOpenExternalLinks(True)
            i.show()
            i.lower()
        #向上滚动至之前位置
        wheel_pos = self.LabelList_pos
        self.LabelList_pos=0
        for i in range(abs(wheel_pos)):
            if i != -self.LabelList_pos:
                break
            self.wheelEvent(int(wheel_pos / abs(wheel_pos)))
        log("RefreshLabelList:"+','.join([i.text() for i in self.LabelList]))
        MutLabelList.unlock()
        return ret

    def StartSearchName(self,name:str=""):
        """
        开始一轮搜索。
        """
        global current_thread_set
        if name=="":
            name = self.EdtName.text()
        #检查这是否是一个合适的name
        name = name.strip(" \n\"\'“”‘’")
        if name == "":
            self.statusBar().showMessage("错误:没有输入名字")
            return - 1
        #如果是一个zkb链接
        fetch = re.search(r"https://zkillboard\.com/character/([0-9]*)/", name)
        if fetch != None:
            log("导入zkb链接"+fetch.group(0))
            self.RefreshLabelList({"LoadFromzkbLink": [TMsgEntry(
                "导入zkb链接 " + fetch.group(0),
                style_str=MDStyleStr(color=settings["clHint"], font_size=settings["labelFontSize"])
            ),TMsgEntry(
                "正在搜索...",
                style_str=MDStyleStr(color=settings["clHint"], font_size=settings["labelFontSize"])
            )]})
            self.MultiThreadRun(func=SearchName, args=(None, int(fetch.group(1))))
        else:
            name=re.search(r"^[a-zA-Z0-9 '\-_]*$", name)
            if name==None:
                #名字中含有非法字符
                self.statusBar().showMessage("错误:名字中含有非法字符")
                return - 1
            else:
                name=name.group(0)

            self.MsgEntryList = {}
            current_thread_set = set()
            log("搜索"+name)
            self.RefreshLabelList({"SearchName": {
                "Searching": [name,
                    TMsgEntry("正在搜索名字包含" + name + '的角色...',
                        style_str=MDStyleStr(
                            color=settings["clHint"],
                            font_size=settings["labelFontSize"]
                        )
                    ),
                ]
            }})
            self.MultiThreadRun(func=SearchName, args=(name,))
        return 0
Beispiel #16
0
class GalacteekApplication(QApplication):
    """
    Galacteek application class

    :param bool debug: enable debugging
    :param str profile: application profile
    """

    manualAvailable = pyqtSignal(str, dict)
    messageDisplayRequest = pyqtSignal(str, str)
    appImageImported = pyqtSignal(str)

    dbConfigured = AsyncSignal(bool)

    def __init__(self,
                 debug=False,
                 profile='main',
                 sslverify=True,
                 enableOrbital=False,
                 progName=None,
                 cmdArgs={}):
        QApplication.__init__(self, sys.argv)

        QCoreApplication.setApplicationName(GALACTEEK_NAME)

        self.dbConfigured.connectTo(self.onDbConfigured)

        self.setQuitOnLastWindowClosed(False)

        self._cmdArgs = cmdArgs
        self._debugEnabled = debug
        self._appProfile = profile
        self._loop = None
        self._executor = None
        self._ipfsClient = None
        self._ipfsOpMain = None
        self._ipfsd = None
        self._sslverify = sslverify
        self._progName = progName
        self._progCid = None
        self._system = platform.system()
        self._urlSchemes = {}
        self._shuttingDown = False

        self._icons = {}
        self._ipfsIconsCache = {}
        self._ipfsIconsCacheMax = 32

        self.enableOrbital = enableOrbital
        self.orbitConnector = None

        self.translator = None
        self.mainWindow = None
        self.feedFollowerTask = None

        self.webProfiles = {}
        self.ipfsCtx = IPFSContext(self)
        self.peersTracker = peers.PeersTracker(self.ipfsCtx)

        self.desktopWidget = QDesktopWidget()
        self.desktopGeometry = self.desktopWidget.screenGeometry()

        self.setWindowIcon(getIcon('galacteek.png'))

        self.setupAsyncLoop()
        self.setupPaths()

    @property
    def cmdArgs(self):
        return self._cmdArgs

    @property
    def shuttingDown(self):
        return self._shuttingDown

    @property
    def offline(self):
        return self.cmdArgs.offline

    @property
    def system(self):
        return self._system

    @property
    def debugEnabled(self):
        return self._debugEnabled

    @property
    def ipfsIconsCacheMax(self):
        return self._ipfsIconsCacheMax

    @property
    def ipfsIconsCache(self):
        return self._ipfsIconsCache

    @property
    def progName(self):
        return self._progName

    @property
    def progCid(self):
        return self._progCid

    @property
    def sslverify(self):
        return self._sslverify

    @property
    def appProfile(self):
        return self._appProfile

    @property
    def ipfsd(self):
        return self._ipfsd

    @property
    def loop(self):
        return self._loop

    @property
    def executor(self):
        return self._executor

    @loop.setter
    def loop(self, newLoop):
        self._loop = newLoop

    @property
    def allTasks(self):
        return asyncio.Task.all_tasks(loop=self.loop)

    @property
    def pendingTasks(self):
        return [task for task in self.allTasks if not task.done()]

    @property
    def ipfsClient(self):
        return self._ipfsClient

    @ipfsClient.setter
    def ipfsClient(self, client):
        self.debug('IPFS client changed: {}'.format(client))
        self._ipfsClient = client

    @property
    def ipfsOpMain(self):
        return self._ipfsOpMain

    @ipfsOpMain.setter
    def ipfsOpMain(self, op):
        """ The main IPFS operator, used by @ipfsOp """
        self.debug('Main IPFS operator upgrade: ID {}'.format(op.uid))
        self._ipfsOpMain = op

    @property
    def gatewayAuthority(self):
        params = self.getIpfsConnectionParams()
        return '{0}:{1}'.format(params.host, params.gatewayPort)

    @property
    def gatewayUrl(self):
        params = self.getIpfsConnectionParams()
        return params.gatewayUrl

    @property
    def dataLocation(self):
        return self._dataLocation

    @property
    def ipfsBinLocation(self):
        return self._ipfsBinLocation

    @property
    def ipfsDataLocation(self):
        return self._ipfsDataLocation

    @property
    def nsCacheLocation(self):
        return self._nsCacheLocation

    @property
    def orbitDataLocation(self):
        return self._orbitDataLocation

    def applyStyle(self, theme='default'):
        qssPath = ":/share/static/qss/{theme}/galacteek.qss".format(
            theme=theme)
        qssFile = QFile(qssPath)

        try:
            qssFile.open(QFile.ReadOnly)
            styleSheetBa = qssFile.readAll()
            styleSheetStr = styleSheetBa.data().decode('utf-8')
            self.setStyleSheet(styleSheetStr)
        except BaseException:
            # that would probably occur if the QSS is not
            # in the resources file..  set some default stylesheet here?
            pass

        self.gStyle = GalacteekStyle()
        self.setStyle(self.gStyle)

    def debug(self, msg):
        if self.debugEnabled:
            log.debug(msg)

    def initSystemTray(self):
        self.systemTray = QSystemTrayIcon(self)
        self.systemTray.setIcon(getIcon('galacteek-incandescent.png'))
        self.systemTray.show()
        self.systemTray.activated.connect(self.onSystemTrayIconClicked)

        systemTrayMenu = QMenu(self.mainWindow)

        actionShow = systemTrayMenu.addAction('Show')
        actionShow.setIcon(getIcon('galacteek-incandescent.png'))
        actionShow.triggered.connect(self.onShowWindow)

        systemTrayMenu.addSeparator()

        actionQuit = systemTrayMenu.addAction('Quit')
        actionQuit.setIcon(getIcon('quit.png'))
        actionQuit.triggered.connect(self.onExit)

        self.systemTray.setContextMenu(systemTrayMenu)

    def initMisc(self):
        self.multihashDb = IPFSObjectMetadataDatabase(self._mHashDbLocation,
                                                      loop=self.loop)

        self.resourceOpener = IPFSResourceOpener(parent=self)

        self.downloadsManager = downloads.DownloadsManager(self)
        self.marksLocal = IPFSMarks(self.localMarksFileLocation, backup=True)

        self.marksLocal.addCategory('general')
        self.marksLocal.addCategory('uncategorized')
        self.marksNetwork = IPFSMarks(self.networkMarksFileLocation,
                                      autosave=False)

        self.tempDir = QTemporaryDir()
        self.tempDirWeb = self.tempDirCreate(self.tempDir.path(),
                                             'webdownloads')

    def tempDirCreate(self, basedir, name=None):
        tmpdir = QDir(basedir)

        if not tmpdir.exists():
            return

        uid = name if name else str(uuid.uuid4())

        path = tmpdir.absoluteFilePath(uid)
        if tmpdir.mkpath(path):
            return path

    async def setupHashmarks(self):
        pkg = 'galacteek.hashmarks.default'

        res = await database.hashmarkSourceSearch(
            name='core', url=pkg, type=models.HashmarkSource.TYPE_PYMODULE)

        if not res:
            await database.hashmarkSourceAdd(
                type=models.HashmarkSource.TYPE_PYMODULE, url=pkg, name='core')
            await self.hmSynchronizer.sync()

        await database.hashmarkSourceAdd(
            type=models.HashmarkSource.TYPE_GITREPOS,
            url='https://github.com/galacteek/hashmarks-dwebland')

        await self.scheduler.spawn(self.hmSynchronizer.syncTask())

    def setupTranslator(self):
        if self.translator:
            QApplication.removeTranslator(self.translator)

        self.translator = QTranslator()
        QApplication.installTranslator(self.translator)
        lang = self.settingsMgr.getSetting(CFG_SECTION_UI, CFG_KEY_LANG)
        self.translator.load(
            ':/share/translations/galacteek_{0}.qm'.format(lang))

    def createMainWindow(self, show=True):
        self.mainWindow = mainui.MainWindow(self)
        if show is True:
            self.mainWindow.show()

    def onSystemTrayIconClicked(self, reason):
        if reason == QSystemTrayIcon.Unknown:
            pass
        elif reason == QSystemTrayIcon.Context:
            pass
        elif reason == QSystemTrayIcon.DoubleClick:
            self.mainWindow.showMaximized()
        else:
            pass

    def systemTrayMessage(self,
                          title,
                          message,
                          timeout=2000,
                          messageIcon=QSystemTrayIcon.Information):
        self.systemTray.showMessage(title, message, messageIcon, timeout)

    @ipfsOp
    async def setupRepository(self, op):
        pubsubEnabled = True  # mandatory now ..
        hExchEnabled = self.settingsMgr.isTrue(CFG_SECTION_IPFS,
                                               CFG_KEY_HASHMARKSEXCH)

        self.ipfsCtx.resources['ipfs-logo-ice'] = await self.importQtResource(
            '/share/icons/ipfs-logo-128-ice.png')
        self.ipfsCtx.resources['ipfs-cube-64'] = await self.importQtResource(
            '/share/icons/ipfs-cube-64.png')
        self.ipfsCtx.resources['atom-feed'] = await self.importQtResource(
            '/share/icons/atom-feed.png')
        self.ipfsCtx.resources['markdown-reference'] = \
            await self.importQtResource(
                '/share/static/docs/markdown-reference.html')

        await self.ipfsCtx.setup(pubsubEnable=pubsubEnabled,
                                 pubsubHashmarksExch=hExchEnabled,
                                 offline=self.offline)
        await self.ipfsCtx.profilesInit()
        await self.qSchemeHandler.start()
        await self.importLdContexts()

        self.feedFollower = FeedFollower(self)
        self.feedFollowerTask = await self.scheduler.spawn(
            self.feedFollower.process())

        await self.ipfsCtx.ipfsRepositoryReady.emit()
        self.ipfsCtx._ipfsRepositoryReady.emit()

        #
        # If the application's binary name is a valid CID, pin it!
        # This happens when running the AppImage and ensures
        # self-seeding of the image!
        #

        if isinstance(self.progName, str):
            progNameClean = re.sub(r'[\.\/]*', '', self.progName)
            if cidhelpers.cidValid(progNameClean):
                self._progCid = progNameClean
                log.debug("Auto pinning program's CID: {0}".format(
                    self.progCid))
                await self.ipfsCtx.pin(joinIpfs(self.progCid),
                                       False,
                                       self.onAppReplication,
                                       qname='self-seeding')

        if self.cmdArgs.seed and self.cmdArgs.appimage:
            await self.seedAppImage()

    @ipfsOp
    async def seedAppImage(self, ipfsop):
        # Automatic AppImage seeding

        if os.path.isfile(self.cmdArgs.binarypath):
            log.info(
                'AppImage seeding: {img}'.format(img=self.cmdArgs.binarypath))

            ensure(ipfsop.addPath(self.cmdArgs.binarypath, wrap=True),
                   futcallback=self.onAppSeed)

    def onAppSeed(self, future):
        try:
            replResult = future.result()
        except Exception as err:
            log.debug('AppImage seed: failed', exc_info=err)
        else:
            if isinstance(replResult, dict):
                cid = replResult.get('Hash')

                self.appImageImported.emit(cid)

                log.info('AppImage seed OK: CID {cid}'.format(cid=cid))

    def onAppReplication(self, future):
        try:
            replResult = future.result()
        except Exception as err:
            log.debug('App replication: failed', exc_info=err)
        else:
            log.debug('App replication: success ({result})'.format(
                result=replResult))

    @ipfsOp
    async def importLdContexts(self, ipfsop):
        """
        Import the JSON-LD contexts and associate the
        directory entry with the 'galacteek.ld.contexts' key
        """

        contextsPath = ipfsop.ldContextsRootPath()

        if not os.path.isdir(contextsPath):
            log.debug('LD contexts not found')
            return

        entry = await ipfsop.addPath(contextsPath,
                                     recursive=True,
                                     hidden=False)
        if entry:
            ldKeyName = 'galacteek.ld.contexts'
            log.debug('LD contexts sitting at: {}'.format(entry.get('Hash')))
            await ipfsop.keyGen(ldKeyName, checkExisting=True)
            ensure(
                ipfsop.publish(entry['Hash'],
                               key=ldKeyName,
                               allow_offline=True))

    @ipfsOp
    async def importQtResource(self, op, path):
        rscFile = QFile(':{0}'.format(path))

        try:
            rscFile.open(QFile.ReadOnly)
            data = rscFile.readAll().data()
            entry = await op.addBytes(data)
        except Exception as e:
            log.debug('importQtResource: {}'.format(str(e)))
        else:
            return entry

    def ipfsTask(self, fn, *args, **kw):
        """ Schedule an async IPFS task """
        return self.loop.create_task(fn(self.ipfsClient, *args, **kw))

    def ipfsTaskOp(self, fn, *args, **kw):
        """ Schedule an async IPFS task using an IPFSOperator instance """
        client = self.ipfsClient
        if client:
            return self.loop.create_task(
                fn(self.getIpfsOperator(), *args, **kw))

    def getIpfsOperator(self):
        """ Returns a new IPFSOperator with the currently active IPFS client"""
        return IPFSOperator(self.ipfsClient,
                            ctx=self.ipfsCtx,
                            debug=self.debugEnabled,
                            nsCachePath=self.nsCacheLocation)

    def getIpfsConnectionParams(self):
        mgr = self.settingsMgr

        section = CFG_SECTION_IPFSD
        if mgr.isTrue(section, CFG_KEY_ENABLED):
            return IPFSConnectionParams(
                '127.0.0.1', mgr.getSetting(section, CFG_KEY_APIPORT),
                mgr.getSetting(section, CFG_KEY_HTTPGWPORT))
        else:
            section = CFG_SECTION_IPFSCONN1
            return IPFSConnectionParams(
                mgr.getSetting(section, CFG_KEY_HOST),
                mgr.getSetting(section, CFG_KEY_APIPORT),
                mgr.getSetting(section, CFG_KEY_HTTPGWPORT))

    def getEthParams(self):
        mgr = self.settingsMgr
        provType = mgr.getSetting(CFG_SECTION_ETHEREUM, CFG_KEY_PROVIDERTYPE)
        rpcUrl = mgr.getSetting(CFG_SECTION_ETHEREUM, CFG_KEY_RPCURL)
        return EthereumConnectionParams(rpcUrl, provType=provType)

    async def updateIpfsClient(self):
        from galacteek.ipfs import ConnectionError

        connParams = self.getIpfsConnectionParams()
        client = aioipfs.AsyncIPFS(host=connParams.host,
                                   port=connParams.apiPort,
                                   loop=self.loop)
        self.ipfsClient = client
        self.ipfsCtx.ipfsClient = client
        self.ipfsOpMain = self.getIpfsOperator()
        self.ipfsOpMain.ipidManager = self.ipidManager

        IPFSOpRegistry.regDefault(self.ipfsOpMain)

        try:
            await self.setupRepository()
        except ConnectionError:
            await messageBoxAsync(
                'IPFS connection error (is your daemon running ?)')

        await self.ipfsCtx.ipfsConnectionReady.emit()

    async def stopIpfsServices(self):
        try:
            await self.ipfsCtx.shutdown()
        except BaseException as err:
            log.debug(
                'Error shutting down context: {err}'.format(err=str(err)))

        if self.feedFollowerTask is not None:
            await self.feedFollowerTask.close()

    def setupDb(self):
        ensure(self.setupOrmDb(self._mainDbLocation))

    def jobsExceptionHandler(self, scheduler, context):
        pass

    async def setupOrmDb(self, dbpath):
        self.scheduler = await aiojobs.create_scheduler(close_timeout=1.0,
                                                        limit=150,
                                                        pending_limit=1000)

        # Old database, just for Atom feeds right now

        self.sqliteDb = SqliteDatabase(self._sqliteDbLocation)
        ensure(self.sqliteDb.setup())
        self.modelAtomFeeds = AtomFeedsModel(self.sqliteDb.feeds, parent=self)

        self.urlHistory = history.URLHistory(
            self.sqliteDb,
            enabled=self.settingsMgr.urlHistoryEnabled,
            parent=self)

        if not await database.initOrm(self._mainDbLocation):
            await self.dbConfigured.emit(False)
            return

        await self.setupHashmarks()
        await self.dbConfigured.emit(True)

    async def onDbConfigured(self, configured):
        if not configured:
            return

        self.debug('Database ready')

        self.setupClipboard()
        self.setupTranslator()
        self.initSystemTray()
        self.initMisc()
        self.initEthereum()
        self.initWebProfiles()
        self.initDapps()
        self.createMainWindow()
        self.clipboardInit()

        await self.setupIpfsConnection()

    async def fetchGoIpfs(self):
        ipfsPath = self.which('ipfs')
        fsMigratePath = self.which('fs-repo-migrations')

        if fsMigratePath is None or self.cmdArgs.forcegoipfsdl:
            await fetchFsMigrateWrapper(self)

        if ipfsPath is None or self.cmdArgs.forcegoipfsdl:
            path = await fetchGoIpfsWrapper(self)
            if path is None:
                self.systemTrayMessage('Galacteek', iGoIpfsFetchError())

    async def setupIpfsConnection(self):
        sManager = self.settingsMgr

        await self.fetchGoIpfs()

        if sManager.isTrue(CFG_SECTION_IPFSD, CFG_KEY_ENABLED):
            fsMigratePath = shutil.which('fs-repo-migrations')
            hasFsMigrate = fsMigratePath is not None

            if hasFsMigrate is False and self.cmdArgs.migrate is True:
                self.systemTrayMessage('Galacteek', iFsRepoMigrateNotFound())

            enableMigrate = hasFsMigrate is True and \
                self.cmdArgs.migrate is True

            ipfsPath = self.which('ipfs')

            await self.startIpfsDaemon(goIpfsPath=ipfsPath,
                                       migrateRepo=enableMigrate)
        else:
            await self.updateIpfsClient()

    def setupMainObjects(self):
        self.manuals = ManualsManager(self)
        self.mimeDb = QMimeDatabase()
        self.jinjaEnv = defaultJinjaEnv()
        self.solarSystem = SolarSystem()
        self.mimeTypeIcons = preloadMimeIcons()
        self.hmSynchronizer = HashmarksSynchronizer()
        self.ipidManager = IPIDManager(
            resolveTimeout=self.settingsMgr.ipidIpnsTimeout)

        self.towers = {
            'dags': DAGSignalsTower(self),
            'schemes': URLSchemesTower(self),
            'did': DIDTower()
        }

        self.rscAnalyzer = ResourceAnalyzer(parent=self)

        self.messageDisplayRequest.connect(
            lambda msg, title: ensure(messageBoxAsync(msg, title=title)))
        self.appImageImported.connect(lambda cid: ensure(
            messageBoxAsync('AppImage was imported in IPFS!')))

    def setupAsyncLoop(self):
        """
        Install the quamash event loop and enable debugging
        """

        loop = QEventLoop(self)
        asyncio.set_event_loop(loop)
        logging.getLogger('quamash').setLevel(logging.INFO)

        if self.debugEnabled:
            logging.getLogger('asyncio').setLevel(logging.DEBUG)
            loop.set_debug(True)
            warnings.simplefilter('always', ResourceWarning)
            warnings.simplefilter('always', BytesWarning)
            warnings.simplefilter('always', ImportWarning)

        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)

        self.loop = loop
        return loop

    def task(self, fn, *args, **kw):
        return self.loop.create_task(fn(*args, **kw))

    def configure(self):
        self.initSettings()
        self.setupMainObjects()
        self.setupSchemeHandlers()
        self.applyStyle()
        self.setupDb()

    def acquireLock(self):
        lpath = Path(self._pLockLocation)

        if not lpath.exists():
            lpath.touch()

        self.lock = FileLock(lpath, timeout=2)

        try:
            self.lock.acquire()
        except Exception:
            return questionBox(
                'Lock', 'The profile lock could not be acquired '
                '(another instance could be running). Ignore ?')

        return True

    def setupPaths(self):
        qtDataLocation = QStandardPaths.writableLocation(
            QStandardPaths.DataLocation)

        if not qtDataLocation:
            raise Exception('No writable data location found')

        self._dataLocation = os.path.join(qtDataLocation, self._appProfile)

        self._ipfsBinLocation = os.path.join(qtDataLocation, 'ipfs-bin')
        self._ipfsDataLocation = os.path.join(self.dataLocation, 'ipfs')
        self._orbitDataLocation = os.path.join(self.dataLocation, 'orbitdb')
        self._mHashDbLocation = os.path.join(self.dataLocation, 'mhashmetadb')
        self._sqliteDbLocation = os.path.join(self.dataLocation, 'db.sqlite')
        self._pLockLocation = os.path.join(self.dataLocation, 'profile.lock')
        self._mainDbLocation = os.path.join(self.dataLocation,
                                            'db_main.sqlite3')
        self.marksDataLocation = os.path.join(self.dataLocation, 'marks')
        self.uiDataLocation = os.path.join(self.dataLocation, 'ui')
        self.cryptoDataLocation = os.path.join(self.dataLocation, 'crypto')
        self.gpgDataLocation = os.path.join(self.cryptoDataLocation, 'gpg')
        self.localMarksFileLocation = os.path.join(self.marksDataLocation,
                                                   'ipfsmarks.local.json')
        self.networkMarksFileLocation = os.path.join(self.marksDataLocation,
                                                     'ipfsmarks.network.json')
        self.pinStatusLocation = os.path.join(self.dataLocation,
                                              'pinstatus.json')
        self._nsCacheLocation = os.path.join(self.dataLocation, 'nscache.json')

        qtConfigLocation = QStandardPaths.writableLocation(
            QStandardPaths.ConfigLocation)
        self.configDirLocation = os.path.join(qtConfigLocation, GALACTEEK_NAME,
                                              self._appProfile)
        self.settingsFileLocation = os.path.join(
            self.configDirLocation, '{}.conf'.format(GALACTEEK_NAME))

        for dir in [
                self._ipfsDataLocation, self._mHashDbLocation,
                self.ipfsBinLocation, self.marksDataLocation,
                self.cryptoDataLocation, self.gpgDataLocation,
                self.uiDataLocation, self.configDirLocation
        ]:
            if not os.path.exists(dir):
                os.makedirs(dir)

        self.defaultDownloadsLocation = QStandardPaths.writableLocation(
            QStandardPaths.DownloadLocation)

        self.debug('Datapath: {0}, config: {1}, configfile: {2}'.format(
            self._dataLocation, self.configDirLocation,
            self.settingsFileLocation))

        os.environ['PATH'] += os.pathsep + self.ipfsBinLocation

    def which(self, prog='ipfs'):
        path = self.ipfsBinLocation + os.pathsep + os.environ['PATH']
        result = shutil.which(prog, path=path)
        self.debug('Program {0} found at {1}'.format(prog, result))
        return result

    def initSettings(self):
        self.settingsMgr = SettingsManager(path=self.settingsFileLocation)
        setDefaultSettings(self)
        self.settingsMgr.sync()

    async def startIpfsDaemon(self, goIpfsPath='ipfs', migrateRepo=False):
        if self.ipfsd is not None:  # we only support one daemon for now
            return

        pubsubEnabled = True  # mandatory now ..
        corsEnabled = self.settingsMgr.isTrue(CFG_SECTION_IPFSD, CFG_KEY_CORS)

        sManager = self.settingsMgr
        section = CFG_SECTION_IPFSD

        # Instantiate an IPFS daemon using asyncipfsd and
        # start it in a task, monitoring the initialization process

        self._ipfsd = asyncipfsd.AsyncIPFSDaemon(
            self.ipfsDataLocation,
            goIpfsPath=goIpfsPath,
            apiport=sManager.getInt(section, CFG_KEY_APIPORT),
            swarmport=sManager.getInt(section, CFG_KEY_SWARMPORT),
            swarmportQuic=sManager.getInt(section, CFG_KEY_SWARMPORT_QUIC),
            swarmProtos=sManager.swarmProtosList,
            gatewayport=sManager.getInt(section, CFG_KEY_HTTPGWPORT),
            swarmLowWater=sManager.getInt(section, CFG_KEY_SWARMLOWWATER),
            swarmHighWater=sManager.getInt(section, CFG_KEY_SWARMHIGHWATER),
            storageMax=sManager.getInt(section, CFG_KEY_STORAGEMAX),
            gwWritable=sManager.isTrue(section, CFG_KEY_HTTPGWWRITABLE),
            routingMode=sManager.getSetting(section, CFG_KEY_ROUTINGMODE),
            pubsubRouter=sManager.getSetting(section, CFG_KEY_PUBSUB_ROUTER),
            namesysPubsub=sManager.isTrue(section, CFG_KEY_NAMESYS_PUBSUB),
            pubsubSigning=sManager.isTrue(section, CFG_KEY_PUBSUB_USESIGNING),
            fileStore=sManager.isTrue(section, CFG_KEY_FILESTORE),
            nice=sManager.getInt(section, CFG_KEY_NICE),
            pubsubEnable=pubsubEnabled,
            corsEnable=corsEnabled,
            migrateRepo=migrateRepo,
            debug=self.cmdArgs.goipfsdebug,
            offline=self.cmdArgs.offline,
            loop=self.loop)

        await self.scheduler.spawn(self.startIpfsdTask(self.ipfsd))

    async def startIpfsdTask(self, ipfsd):
        started = await ipfsd.start()

        if started is False:
            return self.systemTrayMessage('IPFS', iIpfsDaemonProblem())

        running = False

        logUser.info(iIpfsDaemonStarted())

        # Use asyncio.wait_for to wait for the proto.eventStarted
        # event to be fired.

        for attempt in range(1, 32):
            logUser.info(iIpfsDaemonWaiting(attempt))

            with async_timeout.timeout(1):
                try:
                    await ipfsd.proto.eventStarted.wait()
                except asyncio.CancelledError:
                    continue
                except asyncio.TimeoutError:
                    # Event not set yet, wait again
                    log.debug('IPFSD: timeout occured while waiting for '
                              'daemon to start (attempt: {0})'.format(attempt))
                    continue
                else:
                    # Event was set, good to go
                    logUser.info(iIpfsDaemonReady())
                    running = True
                    break

        if running is True:
            ensure(self.updateIpfsClient())
        else:
            logUser.info(iIpfsDaemonInitProblem())

    def initEthereum(self):
        try:
            from galacteek.dweb.ethereum.ctrl import EthereumController

            self.ethereum = EthereumController(self.getEthParams(),
                                               loop=self.loop,
                                               parent=self,
                                               executor=self.executor)
            if self.settingsMgr.ethereumEnabled:
                ensure(self.ethereum.start())
        except ImportError:
            self.ethereum = MockEthereumController()

    def normalCursor(self):
        cursor = QCursor(Qt.ArrowCursor)
        QApplication.setOverrideCursor(cursor)
        QApplication.changeOverrideCursor(cursor)

    def setupClipboard(self):
        self.appClipboard = self.clipboard()
        self.clipTracker = ClipboardTracker(self, self.appClipboard)

    def clipboardInit(self):
        self.clipTracker.clipboardInit()

    def setClipboardText(self, text):
        self.clipTracker.setText(text)

    def getClipboardText(self):
        return self.clipTracker.getText()

    def initWebProfiles(self):
        self.scriptsIpfs = ipfsClientScripts(self.getIpfsConnectionParams())

        self.webProfiles = {
            'minimal': MinimalProfile(parent=self),
            'ipfs': IPFSProfile(parent=self),
            'web3': Web3Profile(parent=self)
        }

    def availableWebProfilesNames(self):
        return [p.profileName for n, p in self.webProfiles.items()]

    def initDapps(self):
        self.dappsRegistry = DappsRegistry(self.ethereum, parent=self)

    def setupSchemeHandlers(self):
        self.dwebSchemeHandler = DWebSchemeHandlerGateway(self)
        self.ensSchemeHandler = EthDNSSchemeHandler(self)
        self.ensProxySchemeHandler = EthDNSProxySchemeHandler(self)
        self.nativeIpfsSchemeHandler = NativeIPFSSchemeHandler(
            self, noMutexes=self.cmdArgs.noipfsmutexlock)
        self.qSchemeHandler = MultiObjectHostSchemeHandler(self)

    def subUrl(self, path):
        """ Joins the gatewayUrl and path to form a new URL """
        sub = QUrl(str(self.gatewayUrl))
        sub.setPath(path)
        return sub

    def getJinjaTemplate(self, name):
        try:
            tmpl = self.jinjaEnv.get_template(name)
        except jinja2.exceptions.TemplateNotFound:
            return None
        else:
            return tmpl

    @asyncify
    async def checkReleases(self):
        self.debug('Checking for new releases')
        newR = await pypicheck.newReleaseAvailable()
        if newR:
            self.systemTrayMessage('Galacteek',
                                   iNewReleaseAvailable(),
                                   timeout=8000)

    def showTasks(self):
        for task in self.pendingTasks:
            self.debug('Pending task: {}'.format(task))

    def onShowWindow(self):
        self.mainWindow.showMaximized()

    def restart(self):
        ensure(self.restartApp())

    async def restartApp(self):
        from galacteek.guientrypoint import appStarter
        pArgs = self.arguments()

        await self.exitApp()
        time.sleep(1)
        appStarter.startProcess(pArgs)

    def onExit(self):
        ensure(self.exitApp())

    async def shutdownScheduler(self):
        # It ain't that bad. STFS with dignity

        for stry in range(0, 12):
            try:
                async with async_timeout.timeout(2):
                    await self.scheduler.close()
            except asyncio.TimeoutError:
                log.warning('Timeout shutting down the scheduler (not fooled)')
                continue
            else:
                log.debug(f'Scheduler went down (try: {stry})')
                return

    async def exitApp(self):
        self._shuttingDown = True

        self.lock.release()

        self.mainWindow.stopTimers()

        try:
            self.systemTray.hide()
        except:
            pass

        try:
            await self.sqliteDb.close()
            await database.closeOrm()
        except:
            pass

        await self.stopIpfsServices()

        await self.loop.shutdown_asyncgens()
        await self.shutdownScheduler()
        await cancelAllTasks()

        await self.ethereum.stop()

        if self.ipfsClient:
            await self.ipfsClient.close()

        if self.ipfsd:
            self.ipfsd.stop()

        self.mainWindow.close()

        if self.debug:
            self.showTasks()

        self.tempDir.remove()
        self.quit()
class MainWindow(QMainWindow):

    restartOdooMenuItem = QtCore.pyqtSignal()
    stopOdooMenuItem = QtCore.pyqtSignal()
    restartPostgreMenuItem = QtCore.pyqtSignal()
    stopPostgreMenuItem = QtCore.pyqtSignal()

    def __init__(self):
        super(MainWindow, self).__init__()

        if OdooInstallationFound == False:

            if not os.path.isdir(appfolder + '\\Temp'):
                os.makedirs(appfolder + '\\Temp')

            unzipToPath = appfolder + '\\Temp\\Unzip'
            destinationPath = appfolder + "\\Runtime\\Odoo"

            self.downloadFileWorker(mainSettings['github']['downloadPath'])

            if os.path.isfile(appfolder + '\\Temp\\GitHub-Odoo.zip'):
                if not os.path.isdir(appfolder + '\\Temp\\unzip'):
                    os.makedirs(appfolder + '\\Temp\\unzip')

                self.zipFileWorker(appfolder + '\\Temp\\GitHub-Odoo.zip', unzipToPath)
                self.zipWindow.close()

                #Check if the file is etxracted to subfolder - Files on github includes branch name -> Correct this
                countFolders = 0
                extractFolder = None
                for name  in os.listdir(unzipToPath):
                    extractFolder = name
                    countFolders += 1
                if countFolders == 1:
                    shutil.move(unzipToPath + "\\" + extractFolder + "\\", destinationPath)
                self.startCybeSystemsApplication()
        else:
            self.startCybeSystemsApplication()

        if os.path.isdir(appfolder + '\\Temp'):
            shutil.rmtree(appfolder + '\\Temp',ignore_errors=True)

    def zipFileWorker(self,file, destination_folder):
        self.zipWindow = ZipWindow(file, destination_folder)
        self.zipWindow.show()

    def downloadFileWorker(self,url):
        self.httpWin = HttpWindow(url)
        self.httpWin.exec()

    def startCybeSystemsApplication(self):

        #Set Loading TrayIcon
        self.setWindowIcon(QtGui.QIcon(appfolder + '/ressource/icons/icon.png'))

        img = QtGui.QImage()
        img.load(appfolder + '/ressource/icons/icon_loading.png')
        self.pixmap = QtGui.QPixmap.fromImage(img)
        self.icon = QtGui.QIcon()
        self.icon.addPixmap(self.pixmap)
        self.tray = QSystemTrayIcon(self.icon, self)
        self.tray.show()
        traymenu = QMenu()

        #Set Real Icon
        self.tray.hide()
        img = QtGui.QImage()
        img.load(appfolder + '/ressource/icons/icon.png')
        self.pixmap = QtGui.QPixmap.fromImage(img)
        self.icon = QtGui.QIcon()
        self.icon.addPixmap(self.pixmap)
        self.tray = QSystemTrayIcon(self.icon, self)
        self.tray.activated.connect(self.onTrayIconActivated)
        self.tray.setContextMenu(traymenu)
        self.tray.show()

        #Load Stylesheet
        if mainSettings['other']['theme'].lower() != 'default':
            if mainSettings['other']['theme'].lower() == 'steamlike':
                stylesheetFile = open(appfolder + '/ressource/ui/steamlike.stylesheet', "r")
            elif mainSettings['other']['theme'].lower() == 'darkorange':
                stylesheetFile = open(appfolder + '/ressource/ui/darkorange.stylesheet', "r")
            elif mainSettings['other']['theme'].lower() == 'maya':
                stylesheetFile = open(appfolder + '/ressource/ui/maya.stylesheet', "r")
            stylesheet = stylesheetFile.read()
            traymenu.setStyleSheet(stylesheet)
            stylesheetFile.close()

        trayoption_openBrowser_entry = QAction(QtGui.QIcon(self.icon), "Open Odoo", self)
        trayoption_openBrowser_entry.triggered.connect(lambda: webbrowser.open(mainSettings['odoo']['startpage']))
        traymenu.addAction(trayoption_openBrowser_entry)
        trayoption_openBrowser_entry.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/world.png'))

        traymenu.addSeparator()

        tools = traymenu.addMenu('&Odoo')
        tools.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/icon.png'))

        tools_odoo_restart = QAction(QtGui.QIcon(self.icon), "Restart Server", self)
        tools_odoo_restart.triggered.connect(lambda: (self.restartOdooMenuItem.emit()))
        tools.addAction(tools_odoo_restart)
        tools_odoo_restart.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/start_server.png'))

        tools_odoo_stop = QAction(QtGui.QIcon(self.icon), "Stop Server", self)
        tools_odoo_stop.triggered.connect(lambda: (self.stopOdooMenuItem.emit()))
        tools.addAction(tools_odoo_stop)
        tools_odoo_stop.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/stop_server.png'))

        #traymenu.addSeparator()

        tools = traymenu.addMenu('&PostgreSQL')
        tools.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/postgresql.png'))

        tools_postgre_restart = QAction(QtGui.QIcon(self.icon), "Restart Server", self)
        tools_postgre_restart.triggered.connect(lambda: (self.restartPostgreMenuItem.emit()))
        tools.addAction(tools_postgre_restart)
        tools_postgre_restart.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/start_server.png'))

        tools_postgre_stop = QAction(QtGui.QIcon(self.icon), "Stop Server", self)
        tools_postgre_stop.triggered.connect(lambda: (self.stopPostgreMenuItem.emit()))
        tools.addAction(tools_postgre_stop)
        tools_postgre_stop.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/stop_server.png'))

        tools.addSeparator()

        tools_pgadmin = QAction(QtGui.QIcon(self.icon), "pgAdmin III", self)
        tools_pgadmin.triggered.connect(lambda: self.startpgadmin())
        tools.addAction(tools_pgadmin)
        tools_pgadmin.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/cog.png'))

        traymenu.addSeparator()

        trayoption_quickconfig = QAction(QtGui.QIcon(self.icon), "Show Output/Config", self)
        trayoption_quickconfig.triggered.connect(lambda: self.showCommandLineWindowTryOption())
        traymenu.addAction(trayoption_quickconfig)
        trayoption_quickconfig.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/application_osx_terminal.png'))

        traymenu.addSeparator()

        trayoption_exit_entry = QAction(QtGui.QIcon(self.icon), "Exit", self)
        trayoption_exit_entry.triggered.connect(lambda: self.trayOptionExit())
        traymenu.addAction(trayoption_exit_entry)
        trayoption_exit_entry.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/cancel.png'))

        self.tray.showMessage('Odoo is Loading - Please wait','\nLeft click to open CommandWindow\nRight click to open Traymenu')

        self.showCommandLineWindow()

    def startpgadmin(self):
        os.startfile(appfolder + '/Runtime/PostgreSQL/bin/pgAdmin3.exe')

    def showCommandLineWindow(self):
        self.ShowCommandLineWindow=CommandLineWindow(self)
        self.ShowCommandLineWindow.setWindowIcon(QtGui.QIcon(appfolder + '/ressource/icons/icon.png'))
        self.ShowCommandLineWindow.show()
        if mainSettings['other']['minimizeToTray']:
            self.ShowCommandLineWindow.setVisible(False)
        else:
            self.ShowCommandLineWindow.setVisible(True)

    def toggleCommandLineWindow(self):
        if self.ShowCommandLineWindow.isMinimized():
            self.ShowCommandLineWindow.setVisible(True)
            self.ShowCommandLineWindow.showNormal()
            self.ShowCommandLineWindow.activateWindow()
        elif self.ShowCommandLineWindow.isVisible ():
            self.ShowCommandLineWindow.showNormal()
            self.ShowCommandLineWindow.setVisible(False)
        else:
            self.ShowCommandLineWindow.setVisible(True)
            self.ShowCommandLineWindow.showNormal()
            self.ShowCommandLineWindow.activateWindow()

    def showCommandLineWindowTryOption(self):
        self.ShowCommandLineWindow.setVisible(True)
        self.ShowCommandLineWindow.showNormal()
        self.ShowCommandLineWindow.activateWindow()

    def onTrayIconActivated(self,reason):
        if reason == QSystemTrayIcon.DoubleClick:
            pass

        if reason == QSystemTrayIcon.Trigger:
            self.toggleCommandLineWindow()

        if reason == QSystemTrayIcon.Context:
            pass

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def trayOptionExit(self,msgbox=True):
        if msgbox:
            quit_msg = "Stop all running Server ?"
            reply = QMessageBox.question(self.center(), 'Exit',
                quit_msg, QMessageBox.Yes, QMessageBox.No)
            if reply == QMessageBox.Yes:
                confirmed = True
            else:
                confirmed = False
        else:
            confirmed = True

        if confirmed:
            runtimeSettings["closeMainWindow"] = True
            self.ShowCommandLineWindow.setVisible(True)
            app = QApplication.instance()
            app.closeAllWindows()
            self.tray.hide()
            os._exit(1)
Beispiel #18
0
class MainApp(QMainWindow, Ui_MainWindow):

    _translate = QCoreApplication.translate
    tab_list = []
    # TODO - add dutch translation files

    def __init__(self, isolated, *args):
        super(MainApp, self).__init__(*args)
        Lumberjack.info('spawning the <<< MainApp >>> hey says: I am the Main man here see!')
        self.load_settings()
        self.setup_tray(isolated)
        self.dbhelper = DbHelper()

        self.setupUi(self)
        self.iconize_controls()
        self.load_styling()

        self.tabWidget = QTabWidget(self.centralwidget)
        self.tabWidget.setTabsClosable(True)
        self.tabWidget.setMovable(True)
        self.tabWidget.setTabBarAutoHide(True)
        self.tabWidget.setObjectName("tabWidget")
        self.verticalLayout.addWidget(self.tabWidget)

        builderLabel = QLabel('made by: MazeFX Solutions')
        self.statusbar.addPermanentWidget(builderLabel)

        self.menuPAT.triggered.connect(self.handle_menu_event)
        self.menuLists.triggered.connect(self.handle_menu_event)
        self.menuHelp.triggered.connect(self.handle_menu_event)
        self.tabWidget.tabCloseRequested.connect(self.close_tab)
        self.actionHome.trigger()

        self._retranslateUi(self)

    def iconize_controls(self):
        Lumberjack.info('< MainApp > - -> (iconize_controls)')

        homeIcon = qta.icon('fa.home', color='white')
        self.actionHome.setIcon(homeIcon)
        wrenchIcon = qta.icon('fa.wrench', color='white')
        self.actionSettings.setIcon(wrenchIcon)

        bankIcon = qta.icon('fa.bank', color='white')
        self.actionListBankAccounts.setIcon(bankIcon)

        contractIcon = QIcon(':/app_icons/rc/handshake_icon.svg')
        self.actionListContracts.setIcon(contractIcon)

        atIcon = qta.icon('fa.at', color='white')
        self.actionListEmailAddresses.setIcon(atIcon)

        envelopeIcon = qta.icon('fa.envelope', color='white')
        self.actionListLetters.setIcon(envelopeIcon)

        relationIcon = qta.icon('fa.group', color='white')
        self.actionListRelations.setIcon(relationIcon)

        transactionIcon = qta.icon('fa.money', color='white')
        self.actionListTransactions.setIcon(transactionIcon)

        userIcon = qta.icon('fa.user', color='white')
        self.actionListUsers.setIcon(userIcon)

        helpIcon = qta.icon('fa.question', color='white')
        self.actionHelp.setIcon(helpIcon)

        aboutIcon = qta.icon('fa.info', color='white')
        self.actionAbout.setIcon(aboutIcon)

    def setup_tray(self, isolated):
        Lumberjack.info('< MainApp > - -> (setup_tray)')
        self.trayIcon = QSystemTrayIcon(QIcon(':/app_icons/rc/PAT_icon.png'), self)
        self.trayMenu = QMenu(self)
        showAction = self.trayMenu.addAction("Open PAT")
        self.trayMenu.addSeparator()
        exitAction = self.trayMenu.addAction("Exit")
        self.trayIcon.setContextMenu(self.trayMenu)
        self.trayMenu.triggered.connect(self.handle_tray_event)
        self.trayIcon.activated.connect(self.handle_tray_event)
        self.trayIcon.show()
        if isolated:
            self.trayIcon.showMessage('PAT Service', 'PAT service is now running..')

    def handle_tray_event(self, *args):
        Lumberjack.info('< MainApp > - -> (handle_tray_event)')
        print(Fore.MAGENTA + '$! Received a tray action with args: ', args)
        if args[0] == 3:
            self.show()
            return
        elif hasattr(args[0], 'text'):
            print(Fore.MAGENTA + '$! Tray event has text!!')
            if args[0].text() == 'Open PAT':
                self.show()
            elif args[0].text() == 'Exit':
                self.close()

    def _retranslateUi(self, MainWindow):
        pass

    def handle_menu_event(self, *args):
        Lumberjack.info('< MainApp > - -> (handle_menu_event)')
        Lumberjack.debug('(handle_menu_event) - args = {}'.format(args))

        action_text = args[0].text()
        icon = args[0].icon()

        Lumberjack.debug('(handle_menu_event) - Action text selector = {}'.format(action_text))
        print(Fore.MAGENTA + '$! Action text received: ', action_text)

        if action_text == 'Home':
            Lumberjack.info('(handle_menu_event) >User action> :  Adding Home tab to self')
            self.add_tab(HomeTab, 'Home', icon)

        if action_text == 'Settings':
            Lumberjack.info('(handle_menu_event) >User action> :  Showing settings dialog')
            self.show_settings()

        elif action_text == 'Bank accounts':
            Lumberjack.info('(handle_menu_event) >User action> : Adding Bank account List tab to self')
            self.add_tab(BankAccountListTab, 'Bank accounts', icon)

        elif action_text == 'Contracts':
            Lumberjack.info('(handle_menu_event) >User action> : Adding Contracts List tab to self')
            self.add_tab(ContractListTab, 'Contracts', icon)

        elif action_text == 'Email addresses':
            Lumberjack.info('(handle_menu_event) >User action> : Adding EmailAddress List tab to self')
            self.add_tab(EmailAddressListTab, 'Email addresses', icon)

        elif action_text == 'Letters':
            Lumberjack.info('(handle_menu_event) >User action> :  Adding Letter List tab to self')
            self.add_tab(LetterListTab, 'Letters', icon)

        elif action_text == 'Users':
            Lumberjack.info('(handle_menu_event) >User action> :  Adding User List tab to self')
            self.add_tab(UserListTab, 'Users', icon)

        elif action_text == 'Relations':
            Lumberjack.info('(handle_menu_event) >User action> :  Adding Relation List tab to self')
            self.add_tab(RelationListTab, 'Relations', icon)

        elif action_text == 'Transactions':
            Lumberjack.info('(handle_menu_event) >User action> :  Adding Transaction List tab to self')
            self.add_tab(TransactionListTab, 'Transactions', icon)

        elif action_text == 'Help':
            Lumberjack.info('(handle_menu_event) >User action> :  Showing help dialog')
            # TODO - build help dialog and help files

        elif action_text == 'About':
            Lumberjack.info('(handle_menu_event) >User action> :  Showing about dialog')
            # TODO build About dialog.

    def show_settings(self):
        Lumberjack.info('< MainApp > - -> (show_settings)')
        settings_dialog = SettingsDialog()
        settings_dialog.exec_()

    def add_tab(self, tab_cls, tab_name, icon):
        Lumberjack.info('< MainApp > - -> (add_tab)')
        new_tab = tab_cls(self.dbhelper)
        print(Fore.MAGENTA + 'Adding a tab with class: ', str(tab_cls))
        new_tab.setObjectName(str(tab_cls))
        self.tabWidget.addTab(new_tab, icon, self._translate("MainWindow", tab_name))

        print(Fore.MAGENTA + 'New tab added to tab list.')
        self.tabWidget.setCurrentIndex(self.tabWidget.indexOf(new_tab))
        self.tab_list.append(new_tab)

    def close_tab(self, index):
        # TODO - Check if index stays correct when moving tabs around
        requesting_tab = self.tab_list[index]
        print(Fore.MAGENTA + 'requesting tab is: ', requesting_tab)
        if hasattr(requesting_tab, 'form'):
            if requesting_tab.form.edit_mode:
                print(Fore.MAGENTA + 'Tab is in edit mode.')
                requesting_tab.form.toggle_edit_mode(False, None, None)
            if requesting_tab.form.edit_mode is None:
                print(Fore.MAGENTA + 'Tab is now in equil.')
                self.tabWidget.removeTab(index)
                del self.tab_list[index]
        else:
            self.tabWidget.removeTab(index)
            del self.tab_list[index]

    def load_settings(self):
        self.settings = QSettings()

        db_path = self.settings.value('db_base_path')
        db_name = self.settings.value('db_name')

        if db_path is not None and db_name is not None:
            db_file = os.path.join(db_path, db_name)
            Lumberjack.debug('__init__ - db_file = {}'.format(db_file))
            if os.path.exists(db_file):
                return
        Lumberjack.warning('(load_settings) - database not found')
        settings_dialog = SettingsDialog()
        settings_dialog.exec_()
        int_value = self.settings.value('db_type', type=int)
        print(Fore.MAGENTA + "load choosen database setting: %s" % repr(int_value))

    def load_styling(self):
        style.set_window_style(self)

    def closeEvent(self, event):
        print(Fore.MAGENTA + "User has clicked the red x on the main window")
        for tab in self.tab_list:
            if hasattr(tab, 'form'):
                if tab.form.edit_mode:
                    print(Fore.MAGENTA + 'Tab is in edit mode.')
                    tab.form.toggle_edit_mode(False, None, None)

        close_dialog = CloseDialog()
        result = close_dialog.exec_()
        if result == close_dialog.Minimize:
            self.hide()
            event.ignore()
        elif result == close_dialog.Rejected:
            event.ignore()
        elif result == close_dialog.Exit:
            print(Fore.MAGENTA + "Exiting via save dialog, result = ", result)
            self.trayIcon.hide()
            event.accept()
Beispiel #19
0
class ElectrumGui(PrintError):

    @profiler
    def __init__(self, config, daemon, plugins):
        set_language(config.get('language', get_default_language()))
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum.desktop')
        self.gui_thread = threading.current_thread()
        self.config = config
        self.daemon = daemon
        self.plugins = plugins
        self.windows = []
        self.efilter = OpenFileEventFilter(self.windows)
        self.app = QElectrumApplication(sys.argv)
        self.app.installEventFilter(self.efilter)
        self.app.setWindowIcon(read_QIcon("electrum.png"))
        # timer
        self.timer = QTimer(self.app)
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec

        self.nd = None
        self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
        self._num_wizards_in_progress = 0
        self._num_wizards_lock = threading.Lock()
        # init tray
        self.dark_icon = self.config.get("dark_icon", False)
        self.tray = QSystemTrayIcon(self.tray_icon(), None)
        self.tray.setToolTip('Electrum')
        self.tray.activated.connect(self.tray_activated)
        self.build_tray_menu()
        self.tray.show()
        self.app.new_window_signal.connect(self.start_new_window)
        self.set_dark_theme_if_needed()
        run_hook('init_qt', self)

    def set_dark_theme_if_needed(self):
        use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark'
        if use_dark_theme:
            try:
                import qdarkstyle
                self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
            except BaseException as e:
                use_dark_theme = False
                self.print_error('Error setting dark theme: {}'.format(repr(e)))
        # Even if we ourselves don't set the dark theme,
        # the OS/window manager/etc might set *a dark theme*.
        # Hence, try to choose colors accordingly:
        ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)

    def build_tray_menu(self):
        # Avoid immediate GC of old menu when window closed via its action
        if self.tray.contextMenu() is None:
            m = QMenu()
            self.tray.setContextMenu(m)
        else:
            m = self.tray.contextMenu()
            m.clear()
        for window in self.windows:
            submenu = m.addMenu(window.wallet.basename())
            submenu.addAction(_("Show/Hide"), window.show_or_hide)
            submenu.addAction(_("Close"), window.close)
        m.addAction(_("Dark/Light"), self.toggle_tray_icon)
        m.addSeparator()
        m.addAction(_("Exit Electrum"), self.close)

    def tray_icon(self):
        if self.dark_icon:
            return read_QIcon('electrum_dark_icon.png')
        else:
            return read_QIcon('electrum_light_icon.png')

    def toggle_tray_icon(self):
        self.dark_icon = not self.dark_icon
        self.config.set_key("dark_icon", self.dark_icon, True)
        self.tray.setIcon(self.tray_icon())

    def tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if all([w.is_hidden() for w in self.windows]):
                for w in self.windows:
                    w.bring_to_top()
            else:
                for w in self.windows:
                    w.hide()

    def close(self):
        for window in self.windows:
            window.close()

    def new_window(self, path, uri=None):
        # Use a signal as can be called from daemon thread
        self.app.new_window_signal.emit(path, uri)

    def show_network_dialog(self, parent):
        if not self.daemon.network:
            parent.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))
            return
        if self.nd:
            self.nd.on_update()
            self.nd.show()
            self.nd.raise_()
            return
        self.nd = NetworkDialog(self.daemon.network, self.config,
                                self.network_updated_signal_obj)
        self.nd.show()

    def _create_window_for_wallet(self, wallet):
        w = ElectrumWindow(self, wallet)
        self.windows.append(w)
        self.build_tray_menu()
        # FIXME: Remove in favour of the load_wallet hook
        run_hook('on_new_window', w)
        w.warn_if_watching_only()
        return w

    def count_wizards_in_progress(func):
        def wrapper(self: 'ElectrumGui', *args, **kwargs):
            with self._num_wizards_lock:
                self._num_wizards_in_progress += 1
            try:
                return func(self, *args, **kwargs)
            finally:
                with self._num_wizards_lock:
                    self._num_wizards_in_progress -= 1
        return wrapper

    @count_wizards_in_progress
    def start_new_window(self, path, uri, *, app_is_starting=False):
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it'''
        wallet = None
        try:
            wallet = self.daemon.load_wallet(path, None)
        except BaseException as e:
            traceback.print_exc(file=sys.stdout)
            QMessageBox.warning(None, _('Error'),
                                _('Cannot load wallet') + ' (1):\n' + str(e))
            # if app is starting, still let wizard to appear
            if not app_is_starting:
                return
        if not wallet:
            wallet = self._start_wizard_to_select_or_create_wallet(path)
        if not wallet:
            return
        # create or raise window
        try:
            for window in self.windows:
                if window.wallet.storage.path == wallet.storage.path:
                    break
            else:
                window = self._create_window_for_wallet(wallet)
        except BaseException as e:
            traceback.print_exc(file=sys.stdout)
            QMessageBox.warning(None, _('Error'),
                                _('Cannot create window for wallet') + ':\n' + str(e))
            if app_is_starting:
                wallet_dir = os.path.dirname(path)
                path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir))
                self.start_new_window(path, uri)
            return
        if uri:
            window.pay_to_URI(uri)
        window.bring_to_top()
        window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)

        window.activateWindow()
        return window

    def _start_wizard_to_select_or_create_wallet(self, path) -> Optional[Abstract_Wallet]:
        wizard = InstallWizard(self.config, self.app, self.plugins)
        try:
            path, storage = wizard.select_storage(path, self.daemon.get_wallet)
            # storage is None if file does not exist
            if storage is None:
                wizard.path = path  # needed by trustedcoin plugin
                wizard.run('new')
                storage = wizard.create_storage(path)
            else:
                wizard.run_upgrades(storage)
        except (UserCancelled, GoBack):
            return
        except WalletAlreadyOpenInMemory as e:
            return e.wallet
        except (WalletFileException, BitcoinException) as e:
            traceback.print_exc(file=sys.stderr)
            QMessageBox.warning(None, _('Error'),
                                _('Cannot load wallet') + ' (2):\n' + str(e))
            return
        finally:
            wizard.terminate()
        # return if wallet creation is not complete
        if storage is None or storage.get_action():
            return
        wallet = Wallet(storage)
        wallet.start_network(self.daemon.network)
        self.daemon.add_wallet(wallet)
        return wallet

    def close_window(self, window):
        if window in self.windows:
           self.windows.remove(window)
        self.build_tray_menu()
        # save wallet path of last open window
        if not self.windows:
            self.config.save_last_wallet(window.wallet)
        run_hook('on_close_window', window)
        self.daemon.stop_wallet(window.wallet.storage.path)

    def init_network(self):
        # Show network dialog if config does not exist
        if self.daemon.network:
            if self.config.get('auto_connect') is None:
                wizard = InstallWizard(self.config, self.app, self.plugins)
                wizard.init_network(self.daemon.network)
                wizard.terminate()

    def main(self):
        try:
            self.init_network()
        except UserCancelled:
            return
        except GoBack:
            return
        except BaseException as e:
            traceback.print_exc(file=sys.stdout)
            return
        self.timer.start()
        self.config.open_last_wallet()
        path = self.config.get_wallet_path()
        if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):
            return
        signal.signal(signal.SIGINT, lambda *args: self.app.quit())

        def quit_after_last_window():
            # keep daemon running after close
            if self.config.get('daemon'):
                return
            # check if a wizard is in progress
            with self._num_wizards_lock:
                if self._num_wizards_in_progress > 0 or len(self.windows) > 0:
                    return
            self.app.quit()
        self.app.setQuitOnLastWindowClosed(False)  # so _we_ can decide whether to quit
        self.app.lastWindowClosed.connect(quit_after_last_window)

        def clean_up():
            # Shut down the timer cleanly
            self.timer.stop()
            # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html
            event = QtCore.QEvent(QtCore.QEvent.Clipboard)
            self.app.sendEvent(self.app.clipboard(), event)
            self.tray.hide()
        self.app.aboutToQuit.connect(clean_up)

        # main loop
        self.app.exec_()
        # on some platforms the exec_ call may not return, so use clean_up()

    def stop(self):
        self.print_error('closing GUI')
        self.app.quit()
Beispiel #20
0
class BlenderLauncher(QMainWindow, BaseWindow, Ui_MainWindow):
    show_signal = pyqtSignal()
    close_signal = pyqtSignal()

    def __init__(self, app):
        super().__init__()
        self.setupUi(self)
        self.setAcceptDrops(True)

        # Server
        self.server = QLocalServer()
        self.server.listen("blender-launcher-server")
        self.server.newConnection.connect(self.new_connection)

        # Global scope
        self.app = app
        self.favorite = None
        self.status = "None"
        self.app_state = AppState.IDLE
        self.cashed_builds = []
        self.notification_pool = []
        self.windows = [self]
        self.manager = PoolManager(num_pools=50, maxsize=10)
        self.timer = None
        self.started = True
        self.latest_tag = ""
        self.new_downloads = False

        # Setup window
        self.setWindowTitle("Blender Launcher")
        self.app.setWindowIcon(
            QIcon(taskbar_icon_paths[get_taskbar_icon_color()]))

        # Setup font
        QFontDatabase.addApplicationFont(
            ":/resources/fonts/OpenSans-SemiBold.ttf")
        self.font = QFont("Open Sans SemiBold", 10)
        self.font.setHintingPreference(QFont.PreferNoHinting)
        self.app.setFont(self.font)

        # Setup style
        file = QFile(":/resources/styles/global.qss")
        file.open(QFile.ReadOnly | QFile.Text)
        self.style_sheet = QTextStream(file).readAll()
        self.app.setStyleSheet(self.style_sheet)

        # Check library folder
        if is_library_folder_valid() is False:
            self.dlg = DialogWindow(
                self,
                title="Information",
                text="First, choose where Blender<br>builds will be stored",
                accept_text="Continue",
                cancel_text=None,
                icon=DialogIcon.INFO)
            self.dlg.accepted.connect(self.set_library_folder)
        else:
            create_library_folders(get_library_folder())
            self.draw()

    def set_library_folder(self):
        library_folder = Path.cwd().as_posix()
        new_library_folder = QFileDialog.getExistingDirectory(
            self,
            "Select Library Folder",
            library_folder,
            options=QFileDialog.DontUseNativeDialog | QFileDialog.ShowDirsOnly)

        if new_library_folder:
            set_library_folder(new_library_folder)
            self.draw(True)
        else:
            self.app.quit()

    def draw(self, polish=False):
        self.HeaderLayout = QHBoxLayout()
        self.HeaderLayout.setContentsMargins(1, 1, 1, 0)
        self.HeaderLayout.setSpacing(0)
        self.CentralLayout.addLayout(self.HeaderLayout)

        self.SettingsButton = \
            QPushButton(QIcon(":resources/icons/settings.svg"), "")
        self.SettingsButton.setIconSize(QSize(20, 20))
        self.SettingsButton.setFixedSize(36, 32)
        self.SettingsButton.setToolTip("Show settings window")
        self.WikiButton = \
            QPushButton(QIcon(":resources/icons/wiki.svg"), "")
        self.WikiButton.setIconSize(QSize(20, 20))
        self.WikiButton.setFixedSize(36, 32)
        self.WikiButton.setToolTip("Open documentation")
        self.MinimizeButton = \
            QPushButton(QIcon(":resources/icons/minimize.svg"), "")
        self.MinimizeButton.setIconSize(QSize(20, 20))
        self.MinimizeButton.setFixedSize(36, 32)
        self.CloseButton = \
            QPushButton(QIcon(":resources/icons/close.svg"), "")
        self.CloseButton.setIconSize(QSize(20, 20))
        self.CloseButton.setFixedSize(36, 32)
        self.HeaderLabel = QLabel("Blender Launcher")
        self.HeaderLabel.setAlignment(Qt.AlignCenter)

        self.HeaderLayout.addWidget(self.SettingsButton, 0, Qt.AlignLeft)
        self.HeaderLayout.addWidget(self.WikiButton, 0, Qt.AlignLeft)
        self.HeaderLayout.addWidget(self.HeaderLabel, 1)
        self.HeaderLayout.addWidget(self.MinimizeButton, 0, Qt.AlignRight)
        self.HeaderLayout.addWidget(self.CloseButton, 0, Qt.AlignRight)

        self.SettingsButton.setProperty("HeaderButton", True)
        self.WikiButton.setProperty("HeaderButton", True)
        self.MinimizeButton.setProperty("HeaderButton", True)
        self.CloseButton.setProperty("HeaderButton", True)
        self.CloseButton.setProperty("CloseButton", True)

        # Tab layout
        self.TabWidget = QTabWidget()
        self.CentralLayout.addWidget(self.TabWidget)

        self.LibraryTab = QWidget()
        self.LibraryTabLayout = QVBoxLayout()
        self.LibraryTabLayout.setContentsMargins(0, 0, 0, 0)
        self.LibraryTab.setLayout(self.LibraryTabLayout)
        self.TabWidget.addTab(self.LibraryTab, "Library")

        self.DownloadsTab = QWidget()
        self.DownloadsTabLayout = QVBoxLayout()
        self.DownloadsTabLayout.setContentsMargins(0, 0, 0, 0)
        self.DownloadsTab.setLayout(self.DownloadsTabLayout)
        self.TabWidget.addTab(self.DownloadsTab, "Downloads")

        self.LibraryToolBox = BaseToolBoxWidget(self)

        self.LibraryStableListWidget = \
            self.LibraryToolBox.add_list_widget(
                "Stable Releases",
                "LibraryStableListWidget",
                "Nothing to show yet",
                "Commit Time",
                extended_selection=True)
        self.LibraryDailyListWidget = \
            self.LibraryToolBox.add_list_widget(
                "Daily Builds",
                "LibraryDailyListWidget",
                "Nothing to show yet",
                "Commit Time",
                extended_selection=True)
        self.LibraryExperimentalListWidget = \
            self.LibraryToolBox.add_list_widget(
                "Experimental Branches",
                "LibraryExperimentalListWidget",
                "Nothing to show yet",
                "Commit Time",
                extended_selection=True)
        self.LibraryCustomListWidget = \
            self.LibraryToolBox.add_list_widget(
                "Custom Builds",
                "LibraryCustomListWidget",
                "Nothing to show yet",
                "Commit Time",
                show_reload=True,
                extended_selection=True)
        self.LibraryTab.layout().addWidget(self.LibraryToolBox)

        self.DownloadsToolBox = BaseToolBoxWidget(self)

        self.DownloadsStableListWidget = \
            self.DownloadsToolBox.add_list_widget(
                "Stable Releases",
                "DownloadsStableListWidget",
                "No new builds available",
                "Upload Time",
                False)
        self.DownloadsDailyListWidget = \
            self.DownloadsToolBox.add_list_widget(
                "Daily Builds",
                "DownloadsDailyListWidget",
                "No new builds available",
                "Upload Time",)
        self.DownloadsExperimentalListWidget = \
            self.DownloadsToolBox.add_list_widget(
                "Experimental Branches",
                "DownloadsExperimentalListWidget",
                "No new builds available",
                "Upload Time",)
        self.DownloadsTab.layout().addWidget(self.DownloadsToolBox)

        self.LibraryToolBox.setCurrentIndex(get_default_library_page())
        self.DownloadsToolBox.setCurrentIndex(get_default_downloads_page())

        # Connect buttons
        self.SettingsButton.clicked.connect(self.show_settings_window)
        self.WikiButton.clicked.connect(lambda: webbrowser.open(
            "https://github.com/DotBow/Blender-Launcher/wiki"))
        self.MinimizeButton.clicked.connect(self.showMinimized)
        self.CloseButton.clicked.connect(self.close)

        self.StatusBar.setContentsMargins(0, 0, 0, 2)
        self.StatusBar.setFont(self.font)
        self.statusbarLabel = QLabel()
        self.statusbarLabel.setIndent(8)
        self.NewVersionButton = QPushButton()
        self.NewVersionButton.hide()
        self.NewVersionButton.clicked.connect(self.show_update_window)
        self.statusbarVersion = QLabel(self.app.applicationVersion())
        self.statusbarVersion.setToolTip(
            "The version of Blender Laucnher that is currently run")
        self.StatusBar.addPermanentWidget(self.statusbarLabel, 1)
        self.StatusBar.addPermanentWidget(self.NewVersionButton)
        self.StatusBar.addPermanentWidget(self.statusbarVersion)

        # Draw library
        self.draw_library()

        # Setup tray icon context Menu
        quit_action = QAction("Quit", self)
        quit_action.triggered.connect(self.quit)
        hide_action = QAction("Hide", self)
        hide_action.triggered.connect(self.close)
        show_action = QAction("Show", self)
        show_action.triggered.connect(self._show)
        launch_favorite_action = QAction(
            QIcon(":resources/icons/favorite.svg"), "Blender", self)
        launch_favorite_action.triggered.connect(self.launch_favorite)

        tray_menu = QMenu()
        tray_menu.setFont(self.font)
        tray_menu.addAction(launch_favorite_action)
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)

        # Setup tray icon
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(
            QIcon(taskbar_icon_paths[get_taskbar_icon_color()]))
        self.tray_icon.setToolTip("Blender Launcher")
        self.tray_icon.activated.connect(self.tray_icon_activated)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.messageClicked.connect(self._show)
        self.tray_icon.show()

        # Forse style update
        if polish is True:
            self.style().unpolish(self.app)
            self.style().polish(self.app)

        # Show window
        if get_launch_minimized_to_tray() is False:
            self._show()

    def show_update_window(self):
        download_widgets = []

        download_widgets.extend(self.DownloadsStableListWidget.items())
        download_widgets.extend(self.DownloadsDailyListWidget.items())
        download_widgets.extend(self.DownloadsExperimentalListWidget.items())

        for widget in download_widgets:
            if widget.state == DownloadState.DOWNLOADING:
                self.dlg = DialogWindow(
                    self,
                    title="Warning",
                    text="In order to update Blender Launcher<br> \
                    complete all active downloads!",
                    accept_text="OK",
                    cancel_text=None,
                    icon=DialogIcon.WARNING)

                return

        self.tray_icon.hide()
        self.close()
        self.update_window = UpdateWindow(self, self.latest_tag)

    def _show(self):
        platform = get_platform()

        if platform == "Windows":
            self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
            self.show()
            self.setWindowFlags(self.windowFlags() & ~Qt.WindowStaysOnTopHint)
            self.show()
        elif platform == "Linux":
            self.show()
            self.activateWindow()

        self.set_status()
        self.show_signal.emit()

    def show_message(self, message, value=None, type=None):
        if (type == MessageType.DOWNLOADFINISHED
                and get_enable_download_notifications() is False):
            return
        elif (type == MessageType.NEWBUILDS
              and get_enable_new_builds_notifications() is False):
            return

        if value not in self.notification_pool:
            if value is not None:
                self.notification_pool.append(value)
            self.tray_icon.showMessage(
                "Blender Launcher", message,
                QIcon(taskbar_icon_paths[get_taskbar_icon_color()]), 10000)

    def launch_favorite(self):
        try:
            self.favorite.launch()
        except Exception:
            self.dlg = DialogWindow(self,
                                    text="Favorite build not found!",
                                    accept_text="OK",
                                    cancel_text=None)

    def tray_icon_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self._show()
        elif reason == QSystemTrayIcon.MiddleClick:
            self.launch_favorite()

    def quit(self):
        download_widgets = []

        download_widgets.extend(self.DownloadsStableListWidget.items())
        download_widgets.extend(self.DownloadsDailyListWidget.items())
        download_widgets.extend(self.DownloadsExperimentalListWidget.items())

        for widget in download_widgets:
            if widget.state == DownloadState.DOWNLOADING:
                self.dlg = DialogWindow(
                    self,
                    title="Warning",
                    text="Active downloads in progress!<br>\
                    Are you sure you want to quit?",
                    accept_text="Yes",
                    cancel_text="No",
                    icon=DialogIcon.WARNING)

                self.dlg.accepted.connect(self.destroy)
                return

        self.destroy()

    def destroy(self):
        if self.timer is not None:
            self.timer.cancel()

        self.tray_icon.hide()
        self.app.quit()

    def draw_library(self, clear=False):
        self.set_status("Reading local builds")

        if clear:
            self.timer.cancel()
            self.scraper.quit()
            self.DownloadsStableListWidget._clear()
            self.DownloadsDailyListWidget._clear()
            self.DownloadsExperimentalListWidget._clear()
            self.started = True

        self.favorite = None

        self.LibraryStableListWidget._clear()
        self.LibraryDailyListWidget._clear()
        self.LibraryExperimentalListWidget._clear()
        self.LibraryCustomListWidget._clear()

        self.library_drawer = LibraryDrawer()
        self.library_drawer.build_found.connect(self.draw_to_library)
        self.library_drawer.finished.connect(self.draw_downloads)
        self.library_drawer.start()

    def reload_custom_builds(self):
        self.LibraryCustomListWidget._clear()
        self.library_drawer = LibraryDrawer()
        self.library_drawer.build_found.connect(self.draw_to_library)
        self.library_drawer.start()

    def draw_downloads(self):
        for page in self.DownloadsToolBox.pages:
            page.set_info_label_text("Checking for new builds")

        self.cashed_builds.clear()
        self.new_downloads = False
        self.app_state = AppState.CHECKINGBUILDS
        self.set_status("Checking for new builds")
        self.scraper = Scraper(self, self.manager)
        self.scraper.links.connect(self.draw_to_downloads)
        self.scraper.new_bl_version.connect(self.set_version)
        self.scraper.error.connect(self.connection_error)
        self.scraper.finished.connect(self.scraper_finished)
        self.scraper.start()

    def connection_error(self):
        set_locale()
        utcnow = strftime(('%H:%M'), localtime())
        self.set_status("Connection Error at " + utcnow)
        self.app_state = AppState.IDLE

        self.timer = threading.Timer(600.0, self.draw_downloads)
        self.timer.start()

    def scraper_finished(self):
        if self.new_downloads and not self.started:
            self.show_message("New builds of Blender is available!",
                              type=MessageType.NEWBUILDS)

        for list_widget in self.DownloadsToolBox.list_widgets:
            for widget in list_widget.widgets:
                if widget.build_info not in self.cashed_builds:
                    widget.destroy()

        set_locale()
        utcnow = strftime(('%H:%M'), localtime())
        self.set_status("Last check at " + utcnow)
        self.app_state = AppState.IDLE

        for page in self.DownloadsToolBox.pages:
            page.set_info_label_text("No new builds available")

        self.timer = threading.Timer(600.0, self.draw_downloads)
        self.timer.start()
        self.started = False

    def draw_from_cashed(self, build_info):
        if self.app_state == AppState.IDLE:
            for cashed_build in self.cashed_builds:
                if build_info == cashed_build:
                    self.draw_to_downloads(cashed_build, False)
                    return

    def draw_to_downloads(self, build_info, show_new=True):
        if self.started:
            show_new = False

        if build_info not in self.cashed_builds:
            self.cashed_builds.append(build_info)

        branch = build_info.branch

        if (branch == 'stable') or (branch == 'lts'):
            downloads_list_widget = self.DownloadsStableListWidget
            library_list_widget = self.LibraryStableListWidget
        elif branch == 'daily':
            downloads_list_widget = self.DownloadsDailyListWidget
            library_list_widget = self.LibraryDailyListWidget
        else:
            downloads_list_widget = self.DownloadsExperimentalListWidget
            library_list_widget = self.LibraryExperimentalListWidget

        if not library_list_widget.contains_build_info(build_info) and \
                not downloads_list_widget.contains_build_info(build_info):
            item = BaseListWidgetItem(build_info.commit_time)
            widget = DownloadWidget(self, downloads_list_widget, item,
                                    build_info, show_new)
            downloads_list_widget.add_item(item, widget)
            self.new_downloads = True

    def draw_to_library(self, path, show_new=False):
        branch = Path(path).parent.name

        if (branch == 'stable') or (branch == 'lts'):
            list_widget = self.LibraryStableListWidget
        elif branch == 'daily':
            list_widget = self.LibraryDailyListWidget
        elif branch == 'experimental':
            list_widget = self.LibraryExperimentalListWidget
        elif branch == 'custom':
            list_widget = self.LibraryCustomListWidget
        else:
            return

        item = BaseListWidgetItem()
        widget = LibraryWidget(self, item, path, list_widget, show_new)
        list_widget.insert_item(item, widget)

    def set_status(self, status=None):
        if status is not None:
            self.status = status

        self.statusbarLabel.setText("Status: {0}".format(self.status))

    def set_version(self, latest_tag):
        current_tag = self.app.applicationVersion()
        latest_ver = re.sub(r'\D', '', latest_tag)
        current_ver = re.sub(r'\D', '', current_tag)

        if int(latest_ver) > int(current_ver):
            if latest_tag not in self.notification_pool:
                self.NewVersionButton.setText("Update to version {0}".format(
                    latest_tag.replace('v', '')))
                self.NewVersionButton.show()
                self.show_message(
                    "New version of Blender Launcher is available!",
                    latest_tag)

            self.latest_tag = latest_tag

    def show_settings_window(self):
        self.settings_window = SettingsWindow(self)

    def clear_temp(self):
        temp_folder = Path(get_library_folder()) / ".temp"
        self.remover = Remover(temp_folder)
        self.remover.start()

    def closeEvent(self, event):
        event.ignore()
        self.hide()
        self.close_signal.emit()

    def new_connection(self):
        self._show()

    def dragEnterEvent(self, e):
        if e.mimeData().hasFormat('text/plain'):
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e):
        print(e.mimeData().text())
Beispiel #21
0
class Controller(Ui_UI):
    def __init__(self):
        super(Controller, self).__init__()
        # self.init()
    def init(self):
        QApplication.setQuitOnLastWindowClosed(False)
        self.mainWin = QMainWindow()
        self.mainWin.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
        self.setupUi(self.mainWin)
        # print("开始gui创建111")
        # self.mainWin.show()
        self.set_icon()
        self.set_menus()
        self.set_icon_menus()

    def set_icon(self):
        self.icon = QSystemTrayIcon(self.mainWin)
        try:
            # icon_dir = Path("gui")
            # url = "{0}{2}{1}{3}{1}{4}{1}{5}".format(icon_dir.path["base"], icon_dir.path["sep"], "UI", "Data", "Assets", "icon.png")
            icon_dir = Path("Data")
            url = "{0}{2}{1}{3}{1}{4}".format(icon_dir.path["base"], icon_dir.path["sep"], "statics", "images",
                                              "icon.png")
            print(">>>>url", url)
            self.icon.setIcon(QIcon(url))
        except Exception as err:
            print("【err】【Controller ->set_icon】 icon图片文件错误")

    def set_menus(self):
        self.menu = {}
        self.menu["show"] = QAction('&显示(Show)', triggered=self.mainWin.show)
        self.menu["hide"] = QAction('&隐藏(Hide)', triggered=self.mainWin.hide)
        self.menu["exit"] = QAction('&退出(Exit)', triggered=self.exit)

        self.menus = QMenu()
        for act in self.menu:
            self.menus.addAction(self.menu[act])

    def set_icon_menus(self):
        self.icon.setContextMenu(self.menus)
        self.icon.activated.connect(self.click_btn)
        self.icon.show()
        print(">>>>show icon")

    def click_btn(self, reason):
        if reason == 2 or reason == 3:
            self.mainWin.show()

    def exit(self):
        re = QMessageBox.question(self.mainWin, "退出", "是否退出", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if re == QMessageBox.Yes:
            self.mainWin.hide()
            self.icon.hide()
            print("退出成功")
            sys.exit(0)

    def restart(self):
        # if self.main_exit:
        #     self.main_exit()
        python = sys.executable
        os.execl(python, python, *sys.argv)

    def txt_run_status(self, info):
        self.txtIOCenterLink.setText("状态:" + info)

    def txt_run(self, info):
        self.txtLogRun.setText(info)

    def txt_err(self, info):
        self.txtLogErr.setText(info)

    def txt_recv(self, info):
        self.txtIoReq.setText(info)

    def txt_send(self, info):
        self.txtIoSend.setText(info)

    def txt_web(self, info):
        self.txtWeb.setText(info)
Beispiel #22
0
class MainWindow(QMainWindow, Ui_main.Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)

        # 时钟计时器
        self.clockTimer = QTimer()
        self.clockTimer.timeout.connect(self.singer)
        self.clockTimer.start(1 * 1000)

        self.workerTimer = QTimer()
        self.workerTimer.timeout.connect(self.worker)
        self.workerTimer.start(60 * 1000)

        self.setup()
        self.bindSignal()
        self.updateGui()
        self.worker()
        self.painter()

    def setup(self):
        self.setWindowIcon(QIcon(':/logo.png'))

        # 初始化剪贴板
        self.clip = QApplication.clipboard()

        # 闹钟提示框
        self.remindBox = RemindMessageBox(self)

        # 初始化窗口
        self.settingDialog = SettingDialog(self)

        self.tafSender = TafSender(self)
        self.trendSender = TrendSender(self)
        self.sigmetSender = SigmetSender(self)
        self.reSender = ReSender(self)

        self.tafEditor = TafEditor(self, self.tafSender)
        self.trendEditor = TrendEditor(self, self.trendSender)
        self.sigmetEditor = SigmetEditor(self, self.sigmetSender)

        if boolean(conf.value('General/Serious')):
            self.taskBrowser = TaskBrowser(self)
            self.taskTafSender = TaskTafSender(self)
            self.taskTafEditor = TaskTafEditor(self, self.taskTafSender)

        self.setRecent()
        self.setTable()
        self.setContractMenu()  # 设置切换联系人菜单
        self.setSysTray()
        self.setStatus()
        self.setThread()
        self.setSound()

    def bindSignal(self):
        context.taf.warningSignal.connect(self.dialer)
        context.taf.clockSignal.connect(self.remindTaf)

        # 连接菜单信号
        self.tafAction.triggered.connect(self.tafEditor.show)
        self.trendAction.triggered.connect(self.trendEditor.show)
        self.sigmetAction.triggered.connect(self.sigmetEditor.show)

        # 连接设置对话框的槽
        self.settingAction.triggered.connect(self.settingDialog.exec_)
        self.settingAction.setIcon(QIcon(':/setting.png'))

        self.openDocsAction.triggered.connect(self.openDocs)
        self.reportIssueAction.triggered.connect(self.reportIssue)
        self.checkUpgradeAction.triggered.connect(
            self.checkUpgradeThread.start)

        # 连接关于信息的槽
        self.aboutAction.triggered.connect(self.about)

        # 连接切换联系人的槽
        self.contractsActionGroup.triggered.connect(self.changeContract)
        self.contractsActionGroup.triggered.connect(self.settingDialog.load)

    def setRecent(self):
        self.clock = Clock(self, self.tipsLayout)
        self.tipsLayout.addSpacerItem(
            QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
        self.currentTaf = CurrentTaf(self, self.tipsLayout)
        self.recentFt = RecentMessage(self, self.recentLayout, 'FT')
        self.recentFc = RecentMessage(self, self.recentLayout, 'FC')
        self.recentSigmet = RecentMessage(self, self.recentLayout, 'WS')
        self.recentTrend = RecentMessage(self, self.recentLayout, 'TREND')

    def setTable(self):
        self.tafTable = TafTable(self, self.tafLayout)
        self.metarTable = MetarTable(self, self.metarLayout)
        self.sigmetTable = SigmetTable(self, self.sigmetLayout)

    def setContractMenu(self):
        self.contractsActionGroup = QActionGroup(self)
        self.contractsActionGroup.addAction(self.contractNo)

        contacts = db.query(User).all()

        for person in contacts:
            setattr(self, 'contract' + str(person.id), QAction(self))
            target = getattr(self, 'contract' + str(person.id))
            target.setText(person.name)
            target.setCheckable(True)

            self.contractsActionGroup.addAction(target)
            self.contractsMenu.addAction(target)

        self.updateContractMenu()

    def setSysTray(self):
        # 设置系统托盘
        self.tray = QSystemTrayIcon(self)
        self.tray.setIcon(QIcon(':/logo.png'))
        self.tray.show()

        # 连接系统托盘的槽
        self.tray.activated.connect(self.restoreWindow)

        self.trayMenu = QMenu(self)

        self.trayMenu.addAction(self.contractsMenu.menuAction())
        self.trayMenu.addAction(self.settingAction)
        self.trayMenu.addAction(self.aboutAction)
        self.trayMenu.addSeparator()
        self.trayMenu.addAction(self.quitAction)

        self.tray.setContextMenu(self.trayMenu)

        title = QCoreApplication.translate(
            'MainWindow', 'Terminal Aerodrome Forecast Encoding Software')
        message = '{} v {}'.format(title, __version__)
        self.tray.setToolTip(message)

    def setStatus(self):
        self.webApiStatus = WebAPIStatus(self, self.statusBar)
        self.callServiceStatus = CallServiceStatus(self,
                                                   self.statusBar,
                                                   last=True)

        # self.statusBar.setStyleSheet('QStatusBar::item{border: 0px}')

    def setThread(self):
        self.workThread = WorkThread(self)
        self.workThread.finished.connect(self.updateMessage)

        self.callThread = CallThread(self)

        self.firInfoThread = FirInfoThread()

        self.checkUpgradeThread = CheckUpgradeThread(self)
        self.checkUpgradeThread.doneSignal.connect(self.checkUpgrade)

    def setSound(self):
        self.ringSound = Sound('ring.wav',
                               conf.value('Monitor/RemindTAFVolume'))
        self.notificationSound = Sound('notification.wav', 100)
        self.alarmSound = Sound('alarm.wav',
                                conf.value('Monitor/WarnTAFVolume'))
        self.trendSound = Sound('trend.wav',
                                conf.value('Monitor/RemindTrendVolume'))
        self.sigmetSound = Sound('sigmet.wav',
                                 conf.value('Monitor/RemindSIGMETVolume'))

        self.settingDialog.warnTafVolume.valueChanged.connect(
            lambda vol: self.alarmSound.play(volume=vol, loop=False))
        self.settingDialog.remindTafVolume.valueChanged.connect(
            lambda vol: self.ringSound.play(volume=vol, loop=False))
        self.settingDialog.remindTrendVolume.valueChanged.connect(
            lambda vol: self.trendSound.play(volume=vol, loop=False))
        self.settingDialog.remindSigmetVolume.valueChanged.connect(
            lambda vol: self.sigmetSound.play(volume=vol, loop=False))

    def changeContract(self):
        target = self.contractsActionGroup.checkedAction()

        if self.contractNo == target:
            conf.setValue('Monitor/SelectedMobile', '')
            logger.info('关闭电话提醒')
        else:
            name = target.text()
            person = db.query(User).filter_by(name=name).first()
            mobile = person.mobile if person else ''

            conf.setValue('Monitor/SelectedMobile', mobile)
            logger.info('切换联系人 %s %s' % (name, mobile))

    def event(self, event):
        if event.type() == QEvent.WindowStateChange and self.isMinimized():
            # 此时窗口已经最小化,
            # 从任务栏中移除窗口
            self.setWindowFlags(self.windowFlags() & Qt.Tool)
            self.tray.show()
            return True
        else:
            return super(self.__class__, self).event(event)

    def keyPressEvent(self, event):
        if boolean(conf.value('General/Serious')):
            if event.modifiers() == (Qt.ShiftModifier | Qt.ControlModifier):
                if event.key() == Qt.Key_P:
                    self.taskTafEditor.show()
                if event.key() == Qt.Key_T:
                    self.taskBrowser.show()

    def closeEvent(self, event):
        if event.spontaneous():
            event.ignore()
            self.hide()
        else:
            self.tafSender.setAttribute(Qt.WA_DeleteOnClose)
            self.trendSender.setAttribute(Qt.WA_DeleteOnClose)
            self.sigmetSender.setAttribute(Qt.WA_DeleteOnClose)
            self.reSender.setAttribute(Qt.WA_DeleteOnClose)
            self.tafEditor.setAttribute(Qt.WA_DeleteOnClose)
            self.trendEditor.setAttribute(Qt.WA_DeleteOnClose)
            self.sigmetEditor.setAttribute(Qt.WA_DeleteOnClose)
            self.settingDialog.setAttribute(Qt.WA_DeleteOnClose)

            self.tafSender.close()
            self.trendSender.close()
            self.sigmetSender.close()
            self.reSender.close()
            self.tafEditor.close()
            self.trendEditor.close()
            self.sigmetEditor.close()
            self.settingDialog.close()

            if boolean(conf.value('General/Serious')):
                self.taskTafSender.setAttribute(Qt.WA_DeleteOnClose)
                self.taskTafEditor.setAttribute(Qt.WA_DeleteOnClose)
                self.taskBrowser.setAttribute(Qt.WA_DeleteOnClose)
                self.taskTafSender.close()
                self.taskTafEditor.close()
                self.taskBrowser.close()

            self.tray.hide()
            event.accept()

    def restoreWindow(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.showNormal()

    def singer(self):
        warnSwitch = self.warnTafAction.isChecked()
        trendSwitch = boolean(conf.value('Monitor/RemindTrend'))

        # 管理趋势声音
        utc = datetime.datetime.utcnow()
        if trendSwitch and utc.minute in (58, 59):
            self.trendSound.play()
        else:
            self.trendSound.stop()

        # 管理报文告警声音
        if warnSwitch and context.taf.isWarning():
            self.alarmSound.play()
        else:
            self.alarmSound.stop()

    def worker(self):
        self.workThread.start()

    def painter(self):
        utc = datetime.datetime.utcnow()
        nextTime = utc.replace(microsecond=0, second=0,
                               minute=0) + datetime.timedelta(hours=1,
                                                              minutes=10)
        delta = nextTime - utc
        QTimer.singleShot(delta.total_seconds() * 1000, self.painter)
        if conf.value('Monitor/FirApiURL'):
            self.firInfoThread.start()

    def dialer(self, test=False):
        callSwitch = conf.value('Monitor/SelectedMobile')

        if callSwitch and context.taf.isWarning() or test:
            self.callThread.start()

    def remindTaf(self, tt):
        remindSwitch = boolean(conf.value('Monitor/RemindTAF'))
        if not remindSwitch:
            return None

        state = context.taf.state()
        clock = state[tt]['clock']
        period = state[tt]['period']
        sent = state[tt]['sent']
        warnning = state[tt]['warnning']

        if clock and not warnning and not sent:
            current = tt + period[2:]
            text = QCoreApplication.translate(
                'MainWindow', 'Time to post {}').format(current)
            self.ringSound.play()
            self.remindBox.setText(text)
            ret = self.remindBox.exec_()
            if not ret:
                QTimer.singleShot(1000 * 60 * 5, lambda: self.remindTaf(tt))

            self.ringSound.stop()

    def remindSigmet(self):
        remindSwitch = boolean(conf.value('Monitor/RemindSIGMET'))
        if not remindSwitch:
            return None

        text = QCoreApplication.translate('MainWindow',
                                          'Time to post {}').format('SIGMET')
        self.sigmetSound.play()
        self.remindBox.setText(text)
        ret = self.remindBox.exec_()
        if not ret:
            QTimer.singleShot(1000 * 60 * 5, self.remindSigmet)

        self.sigmetSound.stop()

    def updateMessage(self):
        listen = Listen(parent=self)
        [listen(i) for i in ('FC', 'FT', 'SA', 'SP')]

        self.updateGui()

    def updateGui(self):
        self.updateTable()
        self.updateRecent()
        self.updateContractMenu()

        logger.debug('Update GUI')

    def updateContractMenu(self):
        mobile = conf.value('Monitor/SelectedMobile')
        person = db.query(User).filter_by(mobile=mobile).first()
        if person:
            action = getattr(self, 'contract' + str(person.id), None)
            if action:
                action.setChecked(True)
        else:
            self.contractNo.setChecked(True)

    def updateRecent(self):
        self.currentTaf.updateGui()
        self.recentFt.updateGui()
        self.recentFc.updateGui()
        self.recentSigmet.updateGui()
        self.recentTrend.updateGui()

    def updateTable(self):
        self.tafTable.updateGui()
        self.metarTable.updateGui()
        self.sigmetTable.updateGui()

    def about(self):
        title = QCoreApplication.translate(
            'MainWindow', 'Terminal Aerodrome Forecast Encoding Software')
        head = '<b>{}</b> v <a href="https://github.com/up1and/tafor">{}</a>'.format(
            title, __version__)
        description = QCoreApplication.translate(
            'MainWindow',
            '''The software is used to encode and post terminal aerodrome forecast, trend forecast, 
                    significant meteorological information, monitor the message, return the alarm by sound or telephone'''
        )
        tail = QCoreApplication.translate(
            'MainWindow',
            '''The project is under GPL-2.0 License, Pull Request and Issue are welcome'''
        )
        copyright = '<br/>© UP1AND 2018'
        text = '<p>'.join([head, description, tail, copyright])

        self.showNormal()
        QMessageBox.about(self, title, text)

    def openDocs(self):
        devDocs = os.path.join(BASEDIR, '../docs/_build/html/index.html')
        releaseDocs = os.path.join(BASEDIR, 'docs/_build/html/index.html')

        if os.path.exists(devDocs):
            url = QUrl.fromLocalFile(devDocs)
        elif os.path.exists(releaseDocs):
            url = QUrl.fromLocalFile(releaseDocs)
        else:
            url = QUrl('https://tafor.readthedocs.io')

        QDesktopServices.openUrl(url)

    def reportIssue(self):
        QDesktopServices.openUrl(
            QUrl('https://github.com/up1and/tafor/issues'))

    def checkUpgrade(self, data):
        hasNewVersion = checkVersion(data.get('tag_name', __version__),
                                     __version__)
        if not hasNewVersion:
            return False

        download = 'https://github.com/up1and/tafor/releases'
        title = QCoreApplication.translate('MainWindow', 'Check for Updates')
        text = QCoreApplication.translate(
            'MainWindow',
            'New version found {}, do you want to download now?').format(
                data.get('tag_name'))
        ret = QMessageBox.question(self, title, text)
        if ret == QMessageBox.Yes:
            QDesktopServices.openUrl(QUrl(download))
Beispiel #23
0
class TrayIcon(QObject):

    def __init__(self):
        super().__init__()
        self._supported = QSystemTrayIcon.isSystemTrayAvailable()
        self._context_menu = None
        self._enabled = False
        self._trayicon = None
        self._state_icons = {}
        for state in (
            'disconnected',
            'disabled',
            'enabled',
        ):
            icon = QIcon(':/state-%s.svg' % state)
            if hasattr(icon, 'setIsMask'):
                icon.setIsMask(True)
            self._state_icons[state] = icon
        self._machine = None
        self._machine_state = 'disconnected'
        self._is_running = False
        self._update_state()

    def set_menu(self, menu):
        self._context_menu = menu
        if self._enabled:
            self._trayicon.setContextMenu(menu)

    def log(self, level, message):
        if self._enabled:
            if level <= log.INFO:
                icon = QSystemTrayIcon.Information
                timeout = 10
            elif level <= log.WARNING:
                icon = QSystemTrayIcon.Warning
                timeout = 15
            else:
                icon = QSystemTrayIcon.Critical
                timeout = 25
            self._trayicon.showMessage(__software_name__.capitalize(),
                                       message, icon, timeout * 1000)
        else:
            if level <= log.INFO:
                icon = QMessageBox.Information
            elif level <= log.WARNING:
                icon = QMessageBox.Warning
            else:
                icon = QMessageBox.Critical
            msgbox = QMessageBox()
            msgbox.setText(message)
            msgbox.setIcon(icon)
            msgbox.exec_()

    def is_supported(self):
        return self._supported

    def enable(self):
        if not self._supported:
            return
        self._trayicon = QSystemTrayIcon()
        # On OS X, the context menu is activated with either mouse buttons,
        # and activation messages are still sent, so ignore those...
        if not sys.platform.startswith('darwin'):
            self._trayicon.activated.connect(self._on_activated)
        if self._context_menu is not None:
            self._trayicon.setContextMenu(self._context_menu)
        self._enabled = True
        self._update_state()
        self._trayicon.show()

    def disable(self):
        if not self._enabled:
            return
        self._trayicon.hide()
        self._trayicon = None
        self._enabled = False

    def is_enabled(self):
        return self._enabled

    def update_machine_state(self, machine, state):
        self._machine = machine
        self._machine_state = state
        self._update_state()

    def update_output(self, enabled):
        self._is_running = enabled
        self._update_state()

    clicked = pyqtSignal()

    def _update_state(self):
        if self._machine_state not in ('initializing', 'connected'):
            state = 'disconnected'
        else:
            state = 'enabled' if self._is_running else 'disabled'
        icon = self._state_icons[state]
        if not self._enabled:
            return
        machine_state = _('{machine} is {state}').format(
            machine=_(self._machine),
            state=_(self._machine_state),
        )
        if self._is_running:
            output_state = _('output is enabled')
        else:
            output_state = _('output is disabled')
        self._trayicon.setIcon(icon)
        self._trayicon.setToolTip(
            'Plover:\n- %s\n- %s' % (output_state, machine_state)
        )

    def _on_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.clicked.emit()
class Qt4SysTrayIcon:
    def __init__(self):
        self.snapshots = snapshots.Snapshots()
        self.config = self.snapshots.config
        self.decode = None

        if len(sys.argv) > 1:
            if not self.config.setCurrentProfile(sys.argv[1]):
                logger.warning("Failed to change Profile_ID %s"
                               %sys.argv[1], self)

        self.qapp = qt4tools.createQApplication(self.config.APP_NAME)
        translator = qt4tools.translator()
        self.qapp.installTranslator(translator)
        self.qapp.setQuitOnLastWindowClosed(False)

        import icon
        self.icon = icon
        self.qapp.setWindowIcon(icon.BIT_LOGO)

        self.status_icon = QSystemTrayIcon(icon.BIT_LOGO)
        #self.status_icon.actionCollection().clear()
        self.contextMenu = QMenu()

        self.menuProfileName = self.contextMenu.addAction(_('Profile: "%s"') % self.config.profileName())
        qt4tools.setFontBold(self.menuProfileName)
        self.contextMenu.addSeparator()

        self.menuStatusMessage = self.contextMenu.addAction(_('Done'))
        self.menuProgress = self.contextMenu.addAction('')
        self.menuProgress.setVisible(False)
        self.contextMenu.addSeparator()

        self.btnPause = self.contextMenu.addAction(icon.PAUSE, _('Pause snapshot process'))
        action = lambda: os.kill(self.snapshots.pid(), signal.SIGSTOP)
        self.btnPause.triggered.connect(action)

        self.btnResume = self.contextMenu.addAction(icon.RESUME, _('Resume snapshot process'))
        action = lambda: os.kill(self.snapshots.pid(), signal.SIGCONT)
        self.btnResume.triggered.connect(action)
        self.btnResume.setVisible(False)

        self.btnStop = self.contextMenu.addAction(icon.STOP, _('Stop snapshot process'))
        self.btnStop.triggered.connect(self.onBtnStop)
        self.contextMenu.addSeparator()

        self.btnDecode = self.contextMenu.addAction(icon.VIEW_SNAPSHOT_LOG, _('decode paths'))
        self.btnDecode.setCheckable(True)
        self.btnDecode.setVisible(self.config.snapshotsMode() == 'ssh_encfs')
        self.btnDecode.toggled.connect(self.onBtnDecode)

        self.openLog = self.contextMenu.addAction(icon.VIEW_LAST_LOG, _('View Last Log'))
        self.openLog.triggered.connect(self.onOpenLog)
        self.startBIT = self.contextMenu.addAction(icon.BIT_LOGO, _('Start BackInTime'))
        self.startBIT.triggered.connect(self.onStartBIT)
        self.status_icon.setContextMenu(self.contextMenu)

        self.pixmap = icon.BIT_LOGO.pixmap(24)
        self.progressBar = QProgressBar()
        self.progressBar.setMinimum(0)
        self.progressBar.setMaximum(100)
        self.progressBar.setValue(0)
        self.progressBar.setTextVisible(False)
        self.progressBar.resize(24, 6)
        self.progressBar.render(self.pixmap, sourceRegion = QRegion(0, -14, 24, 6), flags = QWidget.RenderFlags(QWidget.DrawChildren))

        self.first_error = self.config.notify()
        self.popup = None
        self.last_message = None

        self.timer = QTimer()
        self.timer.timeout.connect(self.updateInfo)

        self.ppid = os.getppid()

    def prepairExit(self):
        self.timer.stop()

        if not self.status_icon is None:
            self.status_icon.hide()
            self.status_icon = None

        if not self.popup is None:
            self.popup.deleteLater()
            self.popup = None

        self.qapp.processEvents()

    def run(self):
        self.status_icon.show()
        self.timer.start(500)

        logger.info("[qt4systrayicon] begin loop", self)

        self.qapp.exec_()

        logger.info("[qt4systrayicon] end loop", self)

        self.prepairExit()

    def updateInfo(self):
        if not tools.processAlive(self.ppid):
            self.prepairExit()
            self.qapp.exit(0)
            return

        paused = tools.processPaused(self.snapshots.pid())
        self.btnPause.setVisible(not paused)
        self.btnResume.setVisible(paused)

        message = self.snapshots.takeSnapshotMessage()
        if message is None and self.last_message is None:
            message = (0, _('Working...'))

        if not message is None:
            if message != self.last_message:
                self.last_message = message
                if self.decode:
                    message = (message[0], self.decode.log(message[1]))
                self.menuStatusMessage.setText('\n'.join(tools.wrapLine(message[1],\
                                                                         size = 80,\
                                                                         delimiters = '',\
                                                                         new_line_indicator = '') \
                                                                       ))
                self.status_icon.setToolTip(message[1])

        pg = progress.ProgressFile(self.config)
        if pg.fileReadable():
            pg.load()
            percent = pg.intValue('percent')
            if percent != self.progressBar.value():
                self.progressBar.setValue(percent)
                self.progressBar.render(self.pixmap, sourceRegion = QRegion(0, -14, 24, 6), flags = QWidget.RenderFlags(QWidget.DrawChildren))
                self.status_icon.setIcon(QIcon(self.pixmap))

            self.menuProgress.setText(' | '.join(self.getMenuProgress(pg)))
            self.menuProgress.setVisible(True)
        else:
            self.status_icon.setIcon(self.icon.BIT_LOGO)
            self.menuProgress.setVisible(False)


    def getMenuProgress(self, pg):
        d = (('sent',   _('Sent:')), \
             ('speed',  _('Speed:')),\
             ('eta',    _('ETA:')))
        for key, txt in d:
            value = pg.strValue(key, '')
            if not value:
                continue
            yield txt + ' ' + value

    def onStartBIT(self):
        profileID = self.config.currentProfile()
        cmd = ['backintime-qt4',]
        if not profileID == '1':
            cmd += ['--profile-id', profileID]
        proc = subprocess.Popen(cmd)

    def onOpenLog(self):
        dlg = logviewdialog.LogViewDialog(self, systray = True)
        dlg.decode = self.decode
        dlg.cbDecode.setChecked(self.btnDecode.isChecked())
        dlg.exec_()

    def onBtnDecode(self, checked):
        if checked:
            self.decode = encfstools.Decode(self.config)
            self.last_message = None
            self.updateInfo()
        else:
            self.decode = None

    def onBtnStop(self):
        os.kill(self.snapshots.pid(), signal.SIGKILL)
        self.btnStop.setEnabled(False)
        self.btnPause.setEnabled(False)
        self.btnResume.setEnabled(False)
        self.snapshots.setTakeSnapshotMessage(0, 'Snapshot terminated')
Beispiel #25
0
class DemoImpl(QDialog):
	
	
    def __init__(self, *args):
        super(DemoImpl, self).__init__(*args)

        loadUi('dict2.ui',self)
		
        self.setLayout(self.verticalLayout)
        self.plainTextEdit.setReadOnly(True)
        self.setWindowFlags(self.windowFlags() |
                            Qt.WindowSystemMenuHint |
                            Qt.WindowMinMaxButtonsHint)
        self.trayicon = QSystemTrayIcon()
        self.traymenu = QMenu()

        self.quitAction = QAction('GQuit', self)
        self.quitAction.triggered.connect(self.close)
        self.quitAction.setShortcut(QKeySequence('Ctrl+q'))
        self.addAction(self.quitAction)

        self.traymenu.addAction('&Normal', self.showNormal, QKeySequence('Ctrl+n'))
        self.traymenu.addAction('Mi&nimize', self.showMinimized, QKeySequence('Ctrl+i'))
        self.traymenu.addAction('&Maximum', self.showMaximized, QKeySequence('Ctrl+m'))
        self.traymenu.addAction('&Quit',self.close, QKeySequence('Ctrl+q'))

        self.trayicon.setContextMenu(self.traymenu)

        self.ticon = QIcon('icon_dict2.ico')
        self.trayicon.setIcon(self.ticon)
        self.trayicon.setToolTip('YYDict')
        self.trayicon.activated.connect(self.on_systemTrayIcon_activated)
        self.traymsg_firstshow = True

        self.button1.clicked.connect(self.searchword)
        self.comboBox.activated.connect(self.searchword)

    def changeEvent(self, event):
        if event.type() == QEvent.WindowStateChange:
            if self.isMinimized():
                self.hide()
                self.trayicon.show()
                if self.traymsg_firstshow:
                    self.trayicon.showMessage('', 'YYDict is running', QSystemTrayIcon.Information, 2000)
                    self.traymsg_firstshow = False
            else:
                self.trayicon.hide()
				
    def closeEvent(self, event):
        self.trayicon.hide()

    @pyqtSlot(QSystemTrayIcon.ActivationReason)
    def on_systemTrayIcon_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.activateWindow()	
            self.showNormal()



    @pyqtSlot()
    def searchword(self):
        word = self.lineEdit.text().strip()
        if not len(word):
            self.plainTextEdit.setPlainText('')
            self.lineEdit.setFocus(Qt.MouseFocusReason)
        else:
            self.workThread = WorkThread(word, self.comboBox.currentIndex())
            self.workThread.received.connect(self.updateResult)
            self.workThread.start()
            self.workThread.wait()


    @pyqtSlot('QString')
    def updateResult(self, rt):
        self.plainTextEdit.setPlainText(rt)
        self.lineEdit.selectAll()
        self.lineEdit.setFocus(Qt.MouseFocusReason)
Beispiel #26
0
class MyApp(QtWidgets.QMainWindow):
	mouseLeaveTimer=0

	def __init__(self):
		# Ui_MainWindow.__init__(self)
		#自己有__init__函数时,不会默认调用基类的__init__函数
		# 因为这里重写了__init__将基类的覆盖掉了,故需要主动调用之
		
		# QtWidgets.QMainWindow.__init__(self) 
		# super(MyApp,self).__init__()
		#上面两句的作用是相同的,下面这句是python3的新写法
		super().__init__()
		# 	Get the Screen size
		self.screenWidth=QDesktopWidget().availableGeometry().width()
		self.screenHeight=QDesktopWidget().availableGeometry().height()
		#初始化字体
		font=QFont('黑体')
		font.setPointSize(12)
		app.setFont(font)
		#  ColorSetting
		self.bgColor=QColor(66,66,77,88)

		#
		# self.setupUi(self)
		self.initUI()
		#用来控制半透明的bg面板自动消失
		self.timer=QTimer()
		self.timer.start(30)
		self.setGeometry(0,30,self.screenWidth,self.screenHeight//3)

		#Flagsq
		self.IsMouseHover=False
		self.MouseOver=False
		self.Locked=False
		self.Hidden=False
		self.isDrag=False
		self.isResize=False
		#变量初始化
		GLOBAL.WINDOWWIDTH=self.width()
		GLOBAL.WINDOWHEIGHT=self.height()
		self.bullets=[]
		self.dragPos=QPoint(22,22)
		self.savedName=''
		# self.screenBuffer=QBitmap(GLOBAL.WINDOWWIDTH,GLOBAL.WINDOWHEIGHT)
		# self.bufferPainter=QPainter(self.screenBuffer)
		# self.picture=QPicture()
		# 建立connection和slot的回调连接
		self.createConnections()
		# 连接到nodejs建立的服务器
		self.connect2Server()

	def initUI(self):
		#构建托盘
		self.trayIcon=QSystemTrayIcon(self)
		self.trayIcon.setIcon(QtGui.QIcon("tmpIcon.ico"))
		self.trayIcon.show()
		self.trayIcon.setToolTip('BulletGo')

		# 构建托盘菜单
		action_quit=QAction('退出',self)
		action_quit.triggered.connect(self.exitApp)
		action_switchLock=QAction('锁定/解锁(F6)',self)
		action_switchLock.triggered.connect(self.switchLock)
		action_showHide=QAction('显示/隐藏(F7)',self)
		action_showHide.triggered.connect(lambda:self.switchVisible(self))
		action_Settings=QAction('设置',self)
		action_Settings.triggered.connect(lambda:self.switchVisible(self.settingWindow))
		trayIconMenu=QtWidgets.QMenu(self)
		trayIconMenu.addAction(action_switchLock)
		trayIconMenu.addAction(action_showHide)
		trayIconMenu.addSeparator()
		trayIconMenu.addAction(action_Settings)
		trayIconMenu.addAction(action_quit)

		#设定快捷键
		QtWidgets.QShortcut(QtGui.QKeySequence(\
			QtCore.Qt.Key_F7),self,\
		(lambda:self.switchVisible(self.settingWindow)))
		QtWidgets.QShortcut(QtGui.QKeySequence(\
			QtCore.Qt.Key_F6),self,\
		(self.switchLock))

		self.trayIcon.setContextMenu(trayIconMenu)
		# 保障不按下鼠标也追踪mouseMove事件
		self.setMouseTracking(True)
		self.setMinimumSize(600,260)
		self.setWindowTitle("BulletGo")
		sizeGrip=QtWidgets.QSizeGrip(self)
		self.setWindowFlags(Qt.FramelessWindowHint\
			|Qt.WindowStaysOnTopHint|Qt.Window|\
			Qt.X11BypassWindowManagerHint)
		#Plan A
		self.setAttribute(Qt.WA_TranslucentBackground,True)
		#这一句是给Mac系统用的,防止它绘制(很黯淡的)背景
		self.setAutoFillBackground(False)
		QSizeGrip(self).setVisible(True)
		sizeGrip.setVisible(True)
		#Plan B  失败
		# palette=QPalette()
		# color=QColor(190, 230, 250)
		# color.setAlphaF(0.6)
		# palette.setBrush(self.backgroundRole(), color)
		# self.setPalette(palette)
		# self.setAutoFillBackground(True)
		# self.setBackgroundRole(QPalette.Window)

		#创建房间的Button和 输入框

		self.roomName=QPlainTextEdit()
		self.roomName.setPlaceholderText('请输入房间名')
		# self.roomName.resize(50,20)
		# self.roomName.move(0,0)
		# self.roomName.setBackgroundVisible(False)
		self.createBtn=QPushButton("创建/进入")
		self.hideBtn=QPushButton('隐藏本设置窗口')
		self.hideBtn.clicked.connect(self.joinRoom)
			# lambda:self.switchVisible(self.settingWindow))
		# self.createBtn.resize(50,20)
		# self.move(0,100)
		# self.d
		settingLayout=QVBoxLayout()
		hLayout=QHBoxLayout()
		settingLayout.addWidget(self.roomName)
		hLayout.addWidget(self.hideBtn)
		hLayout.addWidget(self.createBtn)
		self.settingWindow=QWidget()
		# self.hideBtn=setShortcut(QtGui.QKeySequence('Ctrl+B'))
		settingLayout.addLayout(hLayout)
		self.settingWindow.setLayout(settingLayout)
		# Qt.Tool的作用是  不在任务栏显示
		self.settingWindow.setWindowFlags(Qt.FramelessWindowHint|Qt.Tool\
			|Qt.X11BypassWindowManagerHint|Qt.Popup)
		self.roomName.show()
		self.createBtn.show()
		self.settingWindow.resize(160,26)
		self.settingWindow.show()


		# self.btnFire=QPushButton("Fire",self)
		# self.btnFire.resize(60,60)
		# self.btnFire.move(100,30)
		# self.btnFire.show()

		# self.btnLock=QPushButton("Lock",self)
		# self.btnLock.resize(40,40)
		# self.btnLock.move(self.screenWidth/2,30)
		# self.btnLock.setFlat(True)
		# self.btnLock.setIcon(QtGui.QIcon("tmpIcon.png"))
		# self.btnLock.show()



		# self.danmakuEditText=QPlainTextEdit(self)
		# self.danmakuEditText.resize(200,100)
		# self.danmakuEditText.move(100,100)
		# self.danmakuEditText.setBackgroundVisible(False)
		# self.danmakuEditText.show()

	def joinRoom(self):
		name=self.roomName.toPlainText()
		self.socketio.emit('join',name)

	def connect2Server(self):
		self.socketio=SocketIO('115.159.102.76/bulletgo_client',80,LoggingNamespace)
		self.registerEvents()
		# 开新线程监听服务器发来的消息,否则主线程被阻塞
		_thread.start_new_thread(self.socketio.wait,())

	def registerEvents(self):
		self.socketio.on('create_rsp',lambda rsp:self.handleIncomeBullet(rsp))
		self.socketio.on('bullet',lambda msg:self.handleIncomeBullet(msg))
		self.socketio.on('control_msg',lambda msg:print\
			('---control message---  : '+msg))
		self.socketio.on('sys_notification',lambda msg:print\
			('---system notification---  : '+msg))

	def handleIncomeBullet(self,bulletMsg):
		textsAndInfo=self.preProcessText(bulletMsg)
		if(len(textsAndInfo)>1):
			self.fireABullet(textsAndInfo[0],self.genQColorFromStr(textsAndInfo[1]))

	def createRoom_Nodejs(self,name):
		self.socketio.emit('create_room',name)

	def fireBtn(self):
		txt=self.danmakuEditText.toPlainText()
		tmpbullet=Bullet(txt,GLOBAL.ORANGE,random.randrange(9,16,2))
		self.bullets.append(tmpbullet)
		tmpbullet.prepare()
		# print(len(self.bullets))
		# testStr="line1\nline2\nline3"
		# textsAndInfo=self.preProcessText(testStr)
		# print(len(textsAndInfo))
		# print

	


	def fireABullet(self,txt,color=GLOBAL.ORANGE):
		tmpbullet=Bullet(txt,color,random.randrange(12,22,2))
		self.bullets.append(tmpbullet)
		tmpbullet.prepare()

	def createConnections(self):
		self.timer.timeout.connect(self.update)
		# self.btnFire.clicked.connect(self.fireBtn)
		self.createBtn.clicked.connect(\
			lambda:self.createRoom_Nodejs\
				(self.roomName.toPlainText()))
		# self.btnLock.clicked.connect(self.switchLock)
		# self.btnLock.clicked.connect(self.pullMsg)
		self.trayIcon.activated.connect(self.trayClick)

	def switchVisible(self,handle):
		if(handle.isHidden()):
			handle.activateWindow()
		handle.setHidden(not handle.isHidden())

	def trayClick(self,reason):
		#单击事件还没设计好
		# if(reason==QSystemTrayIcon.Trigger):
		# 	self.switchVisible(self)
		if(reason==QSystemTrayIcon.DoubleClick):
			self.switchVisible(self.settingWindow)


	def switchLock(self):
		self.Locked=not self.Locked

	'''这个神奇的用法, 在js中也可用'''
	'''博客搞好后, 这个要单独写个文章'''
	def genQColorFromStr(self,color):
		# print(color)
		return{
			'white':GLOBAL.WHITE,
			'green':GLOBAL.GREEN,
			'red':GLOBAL.RED,
			'pink':GLOBAL.PINK,
			'purple':GLOBAL.PURPLE,
			'darkblue':GLOBAL.DARKBLUE,
			'blue':GLOBAL.BLUE,
			'yellow':GLOBAL.YELLOW,
			'cyan':GLOBAL.CYAN,
			'orange':GLOBAL.ORANGE,
			'':GLOBAL.ORANGE
		}[color]

	def preProcessText(self,string):
		return string.split('`<')

	'''---[deprecated]---'''
	def realPullMsg(self):
		url='http://danmaku.applinzi.com/message.php'
		r =  requests.post(url,data=self.savedName)
		r.encoding='utf-8'
		#预处理收到的字符串  
		# print(r.text)
		# r.te
		textsAndInfo=self.preProcessText(r.text)
		i=0
		# print(textsAndInfo)
		# print(r.text)
		if(len(textsAndInfo)>1):
			while(i<len(textsAndInfo)-1):
				# print(len(textsAndInfo))
				# print('ddddd')
				# print(i)
				self.fireABullet(textsAndInfo[i],self.genQColorFromStr(textsAndInfo[i+1]))
				i+=2

	'''---[deprecated]---'''
	def pullMsg(self):
		_thread.start_new_thread(self.realPullMsg,())

	'''---[deprecated]---'''
	def createRoom(self):
		#编码问题实在天坑!!!
		self.savedName=self.roomName.toPlainText().encode('utf-8')#保存自己的房间号
		postData=self.roomName.toPlainText().encode('utf-8')
		r = requests.post('http://danmaku.applinzi.com/createroom.php',data=postData)
		r.encoding='utf-8'
		self.fireABullet(r.text)
		# print(r.encoding)
		if(len(r.text)==7):
			# 开始自动获取服务器上的消息内容
			self.pullTimer=QTimer()
			self.pullTimer.start(2000)
			self.pullTimer.timeout.connect(self.pullMsg)
		# print(r.content)
		# print(r.text)


	def closeEvent(self,e):
		e.accept()

	def mouseReleaseEvent(self,e):
		if(e.button()==Qt.LeftButton):
			self.isDrag=False
			self.isResize=False

	def mousePressEvent(self,e):
		if e.button()==Qt.LeftButton:
			self.LDown=True
			# self.dragPos=e.globalPos()-self.frameGeometry().topLeft()
			self.dragPos=e.pos()#效果同上,鼠标相对窗口左上角的位置
			
			if(GLOBAL.WINDOWWIDTH-e.pos().x()<16\
			and GLOBAL.WINDOWHEIGHT-e.pos().y()<16):
				self.topLeft=self.frameGeometry().topLeft()
				self.isResize=True
			else:
				if(not self.Locked):
					self.isDrag=True
		# else:
		# 	if e.button()==Qt.RightButton:
		# 		self.exitApp()
		e.accept()

	def mouseMoveEvent(self,e):
		if(GLOBAL.WINDOWWIDTH-e.pos().x()<16\
			and GLOBAL.WINDOWHEIGHT-e.pos().y()<16):
			#更改鼠标样式
			self.setCursor(Qt.SizeFDiagCursor)
		else:
			self.setCursor(Qt.ArrowCursor)
		#如果是Resize,改变窗口大小
		if(self.isResize):
			tmp=e.globalPos()-self.topLeft
			self.move(self.topLeft)
			self.resize(tmp.x(),tmp.y())
		if (self.isDrag):
			self.move(e.globalPos()-self.dragPos)
		e.accept();



	def enterEvent(self,e):
		self.MouseOver=True
		self.IsMouseHover=True
		return super(MyApp,self).enterEvent(e)

	def setMouseHoverFalse(self):
		# if(not self.MouseOver):
		self.IsMouseHover=self.MouseOver


	def leaveEvent(self,e):
		QTimer.singleShot(800,self.setMouseHoverFalse)
		self.MouseOver=False
		return super(MyApp,self).leaveEvent(e)

	def resizeEvent(self,e):
		GLOBAL.WINDOWWIDTH=self.width()
		GLOBAL.WINDOWHEIGHT=self.height()
		# self.screenBuffer=QBitmap(GLOBAL.WINDOWWIDTH,GLOBAL.WINDOWHEIGHT)
		# self.bufferPainter=QPainter(self.screenBuffer)
		# print('resized')
		# self.repaint()
		e.accept()

	def paintEvent(self,e):
		# Get the Painter
		painter=QPainter(self)
		font=QFont('黑体',GLOBAL.BULLETFONTSIZE,QFont.Bold)
		painter.setFont(font)
		#Draw a semi-Transparent rect whose size is the same with this window
		if(self.IsMouseHover and (not self.Locked)):
			painter.fillRect(0,0,GLOBAL.WINDOWWIDTH,GLOBAL.WINDOWHEIGHT\
				,self.bgColor)
		# painter.setBackground(QBrush(QColor(123,222,123,122)))
		#画所有bullet
		for b in self.bullets:
			b.draw(painter)
		for b in self.bullets:
			if(b.IsExpired):
				self.bullets.remove(b)
		# painter.drawPicture(0,0,self.picture)
		# painter.drawText(30,100,"Hello this is a PyQt5 App我也会说中文")
		return super(MyApp,self).paintEvent(e)

	def exitApp(self):
		self.trayIcon.hide()
		sys.exit()
Beispiel #27
0
class Example(QMainWindow):

    def __init__(self):
        super().__init__()

        self.initUI()
        self.trayIcon = None
        self.kill = False
        self.setupTrayIcon()
        try:
            cherrypy_server.start_server()
        except Exception as e:
            logging.getLogger(__name__).warn("Problem starting print server! {}".format(e))
            traceback.print_exc()

    def reallyClose(self):
        self.kill = True
        self.close()

    def openConsole(self):
        from web.server import cfg
        url = r'https://localhost:{}'.format(cfg.port)
        webbrowser.open(url)


    def initUI(self):

        openAction = QAction("&Open Console", self)
        openAction.setShortcut('Ctrl+O')
        openAction.setStatusTip('Open Console')
        openAction.triggered.connect(self.openConsole)

        exitAction = QAction(QIcon(os.path.join('web', 'static', 'img', 'exit-icon-3.png')), '&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.reallyClose)

        self.statusBar()

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(openAction)
        fileMenu.addSeparator()
        fileMenu.addAction(exitAction)

        wrapper = QWidget()
        txt = QTextEdit()
        txt.setReadOnly(True)
        txt.setLineWrapMode(QTextEdit.NoWrap)
        txt.setUndoRedoEnabled(False)
        txt.setAcceptDrops(False)
        txt.setAcceptRichText(False)

        font = txt.font()
        font.setFamily("Courier")
        font.setPointSize(9)
        txt.setFont(font)

        policy = txt.sizePolicy()
        policy.setVerticalStretch(1)
        txt.setSizePolicy(policy)

        layout = QGridLayout()
        layout.addWidget(txt)
        wrapper.setLayout(layout)

        self.setCentralWidget(wrapper)

        def _write(s):
            txt.moveCursor(QTextCursor.End)
            txt.insertPlainText(str(s))
            txt.moveCursor(QTextCursor.End)

        setup_logging(_write)

        self.setGeometry(300, 300, 800, 600)
        self.setWindowTitle('Antix Print Server')

    def setupTrayIcon(self):
        _icon = QIcon(os.path.join('web', 'static', 'img', 'Logo.144x144.png'))
        self.trayIcon = QSystemTrayIcon(_icon, self)
        menu = QMenu(self)
        menu.addAction("Show", self.show)
        menu.addAction("Hide", self.hide)
        menu.addAction("Exit", self.reallyClose)
        self.trayIcon.setContextMenu(menu)
        self.trayIcon.show()
        self.trayIcon.showMessage("Antix Printer Server", "Is running")

    def closeEvent(self, evnt):
        if self.kill:
            self.trayIcon.hide()
            try:
                cherrypy_server.stop_server()
            except Exception as e:
                logging.getLogger(__name__).warn("Problem stopping server! {}".format(e))
            qApp.exit(0)
        else:
            evnt.ignore()
            self.hide()
Beispiel #28
0
class MainWindow(QWidget):
    """ Main window of application. It is represented by icon in
    taskbar.

    Useful attributes
    -----------------
    addWindow : AddWindow object
    addActive : boolean
        If user wants to add new task, 'addWindow' becomes the QWidget for
        it, and while it is active 'addActive' remains True.
    editWindow : EditWindow object
    editActive : boolean
        If user wants to edit tasks, 'editWindow' becomes the QWidget for
        it, and while it is active 'editActive' remains True.
    tray : QSystemTrayIcon object
        Tray icon and all its attributes like context menu and
        activated action.
    timer : QTimer object
        Timer that fires every second. If one reminder's time is up,
        shows message.
    backupTimer : QTimer object
        Timer that fires every 5 minutes. Saves current state of
        tasks file to backup file.

    """
    def __init__(self):
        """ Init GUI and all required things. """
        super().__init__()

        iconAdd = Icon(byte=icons.add).convertToIcon().getIcon()
        self.tray = QSystemTrayIcon(iconAdd, self)

        menu = QMenu()
        menu.addAction(conf.lang.ADD_ACTION,
                       lambda: self.showWindow(QSystemTrayIcon.Trigger))
        menu.addAction(conf.lang.EDIT_ACTION,
                       lambda: self.showWindow('editAction'))
        menu.addSeparator()
        menu.addAction(conf.lang.OPT_ACTION,
                       lambda: self.showWindow('optAction'))

        menu.addAction(conf.lang.RESTORE, self.restore)
        menu.addAction(conf.lang.QUIT, self.quit)

        self.tray.setContextMenu(menu)
        self.tray.activated.connect(self.showWindow)

        self.tray.show()
        self.addWindow = None
        self.addActive = False
        self.editWindow = None
        self.editActive = False
        self.optWindow = None
        self.optActive = False

        self.timer = QTimer()
        self.timer.timeout.connect(self.timerTick)
        self.timer.start(1000)

        self.backup()
        self.backupTimer = QTimer()
        self.backupTimer.timeout.connect(self.backup)
        self.backupTimer.start(conf.backup * 1000)

    def timerTick(self):
        """ Checks tasks entry's time if it is up. Shows message with
        entry's text if its time's up.

        """
        global tasks
        global rewrite
        for entry in tasks:
            date = entry.getDateTime()
            today = dt.datetime.today()
            if (date - today).days < 0:
                tasks.remove(entry)
                class EvMessageBox(QMessageBox):
                    """ QMessageBox with timer.

                    Parameters
                    ----------
                    text : string
                        Text of message.
                    title : string
                        Title of message window.
                    wicon : QIcon object
                        Icon of message window.
                    icon : QMessageBox.Icon int
                        Icon of message body.
                    timeout : int
                        Time for message has being shown.

                    Useful attributes
                    -----------------
                    timer : QTimer object
                        Timer attached to message.

                    """
                    def __init__(self, text, title, wicon, icon, timeout):
                        super().__init__()
                        self.timeout = timeout
                        self.setText(text)
                        self.setWindowTitle(title)
                        self.setWindowIcon(wicon)
                        self.setIcon(icon)
                        self.addButton(QPushButton(conf.lang.REPEAT.format(
                                                   parseSeconds(conf.tdelta))),
                                       QMessageBox.YesRole)
                        self.addButton(QPushButton(conf.lang.CLOSE),
                                       QMessageBox.NoRole)
                        self.setWindowFlags(Qt.WindowStaysOnTopHint)
                        # self.setTextFormat(Qt.RichText)
                        self.timer = QTimer()
                        self.timer.timeout.connect(self.timerTick)

                    def showEvent(self, event):
                        """ Start timer on message showEvent. """
                        self.currentTime = 0
                        self.timer.start(1000)

                    def timerTick(self):
                        """ Done message on timeout. """
                        self.currentTime += 1
                        if self.currentTime >= self.timeout:
                            self.timer.stop()
                            self.done(-1)

                msgBox = EvMessageBox(
                    entry.text,
                    '{} {}'.format(conf.lang.TASK, date),
                    Icon(byte=icons.alert).convertToIcon().getIcon(),
                    QMessageBox.Information,
                    conf.mesout)
                reply = msgBox.exec_()
                msgBox.raise_()

                if reply != 1:
                    td = conf.tdelta if reply == 0 else 300 - conf.mesout
                    date = dateToStr(dt.datetime.now() + dt.timedelta(0, td))
                    date['date'] = date['date'].replace('/', '.')
                    date['time'] = date['time'].replace('.', ':')
                    tasks.append(Entry(date['date'], date['time'], entry.text))
                rewrite()
                if self.editActive:
                    self.editWindow.filterApply()

    def showWindow(self, event):
        """ Show child windows.
        If event is QSystemTrayIcon.Trigger then it checks if
        all windows are not open and show addWindow.

        If event is 'addAction' it means that user from editWindow
        want to edit reminder, so it opens addWindow.

        Then it checks if addAction or editAction is True, and alert
        appropriate window.

        Then if event is 'editAction' then it opens editWindow.

        """
        if event == 'editAction':
            if self.editActive:
                QApplication.alert(self.editWindow)
            else:
                self.editWindow = EditWindow(self)
                self.editWindow.show()
                self.editWindow.setFocus(True)
                self.editWindow.activateWindow()
            return self.editWindow
        elif event == 'addAction':
            self.addWindow = AddWindow(self)
            self.addWindow.show()
            self.addWindow.setFocus(True)
            self.addWindow.activateWindow()
            return self.addWindow
        elif event == QSystemTrayIcon.Trigger:
            if self.addActive:
                QApplication.alert(self.addWindow)
            else:
                self.addWindow = AddWindow(self)
                self.addWindow.show()
                self.addWindow.setFocus(True)
                self.addWindow.activateWindow()
            return self.addWindow
        elif event == 'optAction':
            if self.addActive:
                self.addWindow.hide()
            if self.editActive:
                self.editWindow.hide()
            self.optWindow = OptionsWindow(self)
            self.optWindow.show()
            self.optWindow.setFocus(True)
            self.optWindow.activateWindow()

    def backup(self):
        """ Copies content of tasks file to backup file. """
        with open(backup, 'w') as to_, open(filename, 'r') as from_:
            to_.write(from_.read())

    def restore(self):
        """ Restores content of tasks file from backup file after
        user confirmation.

        """
        global tasks
        shure = QMessageBox.question(self, conf.lang.RESTORE,
                                     conf.lang.RESTORE_TEXT,
                                     QMessageBox.No | QMessageBox.Yes,
                                     QMessageBox.No)

        if shure == QMessageBox.No:
            pass
        else:
            temp = open(backup).read() # don't forget to read backup
            self.backup()
            with open(filename, 'w') as to_:
                to_.write(temp)
            tasks = []
            readTasks()
            if self.editActive:
                self.editWindow.filterApply()

    def quit(self, really=True):
        """ Quits application. Hides tray icon firstly.
        If really is not True, do not quits the application.
        It is used to re-launch the application.

        """
        self.tray.hide()
        self.timer.stop()
        self.backupTimer.stop()
        if really:
            QCoreApplication.instance().quit()
Beispiel #29
0
class ElectrumGui(Logger):

    @profiler
    def __init__(self, config, daemon, plugins):
        set_language(config.get('language', get_default_language()))
        Logger.__init__(self)
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum.desktop')
        self.gui_thread = threading.current_thread()
        self.config = config
        self.daemon = daemon
        self.plugins = plugins
        self.windows = []
        self.efilter = OpenFileEventFilter(self.windows)
        self.app = QElectrumApplication(sys.argv)
        self.app.installEventFilter(self.efilter)
        self.app.setWindowIcon(read_QIcon("electrum.png"))
        # timer
        self.timer = QTimer(self.app)
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec

        self.network_dialog = None
        self.lightning_dialog = None
        self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
        self._num_wizards_in_progress = 0
        self._num_wizards_lock = threading.Lock()
        # init tray
        self.dark_icon = self.config.get("dark_icon", False)
        self.tray = QSystemTrayIcon(self.tray_icon(), None)
        self.tray.setToolTip('Electrum')
        self.tray.activated.connect(self.tray_activated)
        self.build_tray_menu()
        self.tray.show()
        self.app.new_window_signal.connect(self.start_new_window)
        self.set_dark_theme_if_needed()
        run_hook('init_qt', self)

    def set_dark_theme_if_needed(self):
        use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark'
        if use_dark_theme:
            try:
                import qdarkstyle
                self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
            except BaseException as e:
                use_dark_theme = False
                self.logger.warning(f'Error setting dark theme: {repr(e)}')
        # Apply any necessary stylesheet patches
        patch_qt_stylesheet(use_dark_theme=use_dark_theme)
        # Even if we ourselves don't set the dark theme,
        # the OS/window manager/etc might set *a dark theme*.
        # Hence, try to choose colors accordingly:
        ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)

    def build_tray_menu(self):
        # Avoid immediate GC of old menu when window closed via its action
        if self.tray.contextMenu() is None:
            m = QMenu()
            self.tray.setContextMenu(m)
        else:
            m = self.tray.contextMenu()
            m.clear()
        if self.config.get('lightning'):
            m.addAction(_("Lightning"), self.show_lightning_dialog)
        for window in self.windows:
            name = window.wallet.basename()
            submenu = m.addMenu(name)
            submenu.addAction(_("Show/Hide"), window.show_or_hide)
            submenu.addAction(_("Close"), window.close)
        m.addAction(_("Dark/Light"), self.toggle_tray_icon)
        m.addSeparator()
        m.addAction(_("Exit Electrum"), self.close)

    def tray_icon(self):
        if self.dark_icon:
            return read_QIcon('electrum_dark_icon.png')
        else:
            return read_QIcon('electrum_light_icon.png')

    def toggle_tray_icon(self):
        self.dark_icon = not self.dark_icon
        self.config.set_key("dark_icon", self.dark_icon, True)
        self.tray.setIcon(self.tray_icon())

    def tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if all([w.is_hidden() for w in self.windows]):
                for w in self.windows:
                    w.bring_to_top()
            else:
                for w in self.windows:
                    w.hide()

    def close(self):
        for window in self.windows:
            window.close()
        if self.network_dialog:
            self.network_dialog.close()
        if self.lightning_dialog:
            self.lightning_dialog.close()

    def new_window(self, path, uri=None):
        # Use a signal as can be called from daemon thread
        self.app.new_window_signal.emit(path, uri)

    def show_lightning_dialog(self):
        if not self.lightning_dialog:
            self.lightning_dialog = LightningDialog(self)
        self.lightning_dialog.bring_to_top()

    def show_network_dialog(self, parent):
        if not self.daemon.network:
            parent.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))
            return
        if self.network_dialog:
            self.network_dialog.on_update()
            self.network_dialog.show()
            self.network_dialog.raise_()
            return
        self.network_dialog = NetworkDialog(self.daemon.network, self.config,
                                self.network_updated_signal_obj)
        self.network_dialog.show()

    def _create_window_for_wallet(self, wallet):
        w = ElectrumWindow(self, wallet)
        self.windows.append(w)
        self.build_tray_menu()
        # FIXME: Remove in favour of the load_wallet hook
        run_hook('on_new_window', w)
        w.warn_if_testnet()
        w.warn_if_watching_only()
        return w

    def count_wizards_in_progress(func):
        def wrapper(self: 'ElectrumGui', *args, **kwargs):
            with self._num_wizards_lock:
                self._num_wizards_in_progress += 1
            try:
                return func(self, *args, **kwargs)
            finally:
                with self._num_wizards_lock:
                    self._num_wizards_in_progress -= 1
        return wrapper

    @count_wizards_in_progress
    def start_new_window(self, path, uri, *, app_is_starting=False):
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it'''
        wallet = None
        try:
            wallet = self.daemon.load_wallet(path, None)
        except BaseException as e:
            self.logger.exception('')
            custom_message_box(icon=QMessageBox.Warning,
                               parent=None,
                               title=_('Error'),
                               text=_('Cannot load wallet') + ' (1):\n' + repr(e))
            # if app is starting, still let wizard to appear
            if not app_is_starting:
                return
        if not wallet:
            try:
                wallet = self._start_wizard_to_select_or_create_wallet(path)
            except (WalletFileException, BitcoinException) as e:
                self.logger.exception('')
                custom_message_box(icon=QMessageBox.Warning,
                                   parent=None,
                                   title=_('Error'),
                                   text=_('Cannot load wallet') + ' (2):\n' + repr(e))
        if not wallet:
            return
        # create or raise window
        try:
            for window in self.windows:
                if window.wallet.storage.path == wallet.storage.path:
                    break
            else:
                window = self._create_window_for_wallet(wallet)
        except BaseException as e:
            self.logger.exception('')
            custom_message_box(icon=QMessageBox.Warning,
                               parent=None,
                               title=_('Error'),
                               text=_('Cannot create window for wallet') + ':\n' + repr(e))
            if app_is_starting:
                wallet_dir = os.path.dirname(path)
                path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir))
                self.start_new_window(path, uri)
            return
        if uri:
            window.pay_to_URI(uri)
        window.bring_to_top()
        window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)

        window.activateWindow()
        return window

    def _start_wizard_to_select_or_create_wallet(self, path) -> Optional[Abstract_Wallet]:
        wizard = InstallWizard(self.config, self.app, self.plugins)
        try:
            path, storage = wizard.select_storage(path, self.daemon.get_wallet)
            # storage is None if file does not exist
            if storage is None:
                wizard.path = path  # needed by trustedcoin plugin
                wizard.run('new')
                storage = wizard.create_storage(path)
            else:
                wizard.run_upgrades(storage)
        except (UserCancelled, GoBack):
            return
        except WalletAlreadyOpenInMemory as e:
            return e.wallet
        finally:
            wizard.terminate()
        # return if wallet creation is not complete
        if storage is None or storage.get_action():
            return
        wallet = Wallet(storage)
        wallet.start_network(self.daemon.network)
        self.daemon.add_wallet(wallet)
        return wallet

    def close_window(self, window: ElectrumWindow):
        if window in self.windows:
           self.windows.remove(window)
        self.build_tray_menu()
        # save wallet path of last open window
        if not self.windows:
            self.config.save_last_wallet(window.wallet)
        run_hook('on_close_window', window)
        self.daemon.stop_wallet(window.wallet.storage.path)

    def init_network(self):
        # Show network dialog if config does not exist
        if self.daemon.network:
            if self.config.get('auto_connect') is None:
                wizard = InstallWizard(self.config, self.app, self.plugins)
                wizard.init_network(self.daemon.network)
                wizard.terminate()

    def main(self):
        try:
            self.init_network()
        except UserCancelled:
            return
        except GoBack:
            return
        except BaseException as e:
            self.logger.exception('')
            return
        self.timer.start()

        self.config.open_last_wallet()
        path = self.config.get_wallet_path()
        if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):
            return
        signal.signal(signal.SIGINT, lambda *args: self.app.quit())

        def quit_after_last_window():
            # keep daemon running after close
            if self.config.get('daemon'):
                return
            # check if a wizard is in progress
            with self._num_wizards_lock:
                if self._num_wizards_in_progress > 0 or len(self.windows) > 0:
                    return
                if self.config.get('persist_daemon'):
                    return
            self.app.quit()
        self.app.setQuitOnLastWindowClosed(False)  # so _we_ can decide whether to quit
        self.app.lastWindowClosed.connect(quit_after_last_window)

        def clean_up():
            # Shut down the timer cleanly
            self.timer.stop()
            # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html
            event = QtCore.QEvent(QtCore.QEvent.Clipboard)
            self.app.sendEvent(self.app.clipboard(), event)
            self.tray.hide()
        self.app.aboutToQuit.connect(clean_up)

        # main loop
        self.app.exec_()
        # on some platforms the exec_ call may not return, so use clean_up()

    def stop(self):
        self.logger.info('closing GUI')
        self.app.quit()
Beispiel #30
0
class WindowGUI(QWidget):
    def __init__(self):
        super().__init__()
        self.appTitle = 'Keasy'
        self.version = '2.0'
        self.icon_path = 'icon.png'
        self.icon = QIcon(self.resource_path(self.icon_path))
        self.icon2_path = 'icon2.png'
        self.icon2 = QIcon(self.resource_path(self.icon2_path))
        self.command_maxLengthTxt = '60字まで'
        self.tray_toolchipTxt = 'Ctrlキーを2回押すことで展開します'
        self.history = ['']  # コマンド履歴用
        self.setWindowTitle(self.appTitle)
        self.setWindowFlags(Qt.CustomizeWindowHint)  # タイトルバーを消す
        self.setMinimumSize(600, 500)
        self.setMaximumSize(1200, 800)
        self.savedMyGeometry = None  # タスクトレイ格納時のウィンドウサイズ
        self.setStyleSheet('color:rgb(200,200,200);'
                           'background:rgb(50,50,50);')
        # コマンド処理のインスタンス
        self.cmdEvt = CommandEvents()
        self.cmdEvt.setObj(self)
        self.cmdEvt.exitSIGNAL.connect(self.exitKeasy)
        # OSによって適用フォントを変える
        import platform
        MyOS = platform.system()
        if MyOS == 'Windows':
            MainFont = 'font-family:MS Gothic;font-size:14px;'
            ConsoleFont = 'font-family:MS Gothic;font-size:18px;'
        elif MyOS == 'Darwin':
            MainFont = 'font-family:Monospace;font-size:13px;'
            ConsoleFont = 'font-family:Monospace;font-size:16px;'
        else:
            MainFont = 'font-family:Monospace;font-size:14px;'
            ConsoleFont = 'font-family:Monospace;font-size:18px;'
        self.tray = QSystemTrayIcon()  # トレイアイコン(通知領域)
        self.initSystemTray(self.tray)  # トレイ設定
        self.initUI(MainFont, ConsoleFont)  # アプリの外観
        self.show()  # ウィンドウ表示
        # OSによってウィンドウ操作プログラムを変える
        import WindowController
        WindowController.controlWindow(self)
        self.cmdEvt.receiver('')  # 最初にパスワード認証させる

    # pyinstallerでexe/app化した後でも画像を使えるように絶対パスへ変更
    # Win/Macどちらも,この処理をしないと画像・アイコンが表示されない
    def resource_path(self, relative_path):
        if hasattr(sys, '_MEIPASS'):
            return os_path.join(sys._MEIPASS, relative_path)
        return os_path.join(os_path.abspath("."), relative_path)

    # ウィンドウを左クリックすると発動
    def mousePressEvent(self, QMouseEvent):
        """
        :type QMouseEvent:PyQt5.QtGui.QMouseEvent.QMouseEvent
        """
        try:
            # クリック時のマウス座標を保持
            self.previousMousePos = QMouseEvent.pos()
            self.prevX = self.previousMousePos.x()
            self.prevY = self.previousMousePos.y()
        except:
            traceback.print_exc()

    # ウィンドウをマウスドラッグ(左クリックしたままマウス移動)すると発動
    def mouseMoveEvent(self, QMouseEvent):
        """
        :type QMouseEvent:PyQt5.QtGui.QMouseEvent.QMouseEvent
        """
        try:
            # ドラッグ中はマウス座標とウィンドウの座標を取得し続ける
            currentMousePos = QMouseEvent.pos()
            currentX = currentMousePos.x()
            currentY = currentMousePos.y()
            appPos = self.pos()
            appPos_x = appPos.x()
            appPos_y = appPos.y()
        except:
            traceback.print_exc()

        # クリック時と現在のマウス座標差分だけ,今のウィンドウ位置から移動
        # ※クリック時マウス位置は不変
        diffPos_x = self.prevX - currentX
        diffPos_y = self.prevY - currentY
        self.move(appPos_x - diffPos_x, appPos_y - diffPos_y)

    # ウィンドウにフォーカスしている時のキー入力判定
    def keyPressEvent(self, evt):
        try:
            # Tabキーが押された&Ctrl,Shift,Altのどれも押されていない場合,
            # Tabキーイベントを発生させる
            if evt.key() == Qt.Key_Tab and \
                    (not evt.modifiers() & Qt.ControlModifier and
                     not evt.modifiers() & Qt.ShiftModifier and
                     not evt.modifiers() & Qt.AltModifier):
                self.tabEvent()
            pass
        except:
            traceback.print_exc()

    # 現在のコマンド入力内容でTabキーイベントの処理
    def tabEvent(self):
        text = self.console.text()
        self.cmdEvt.tabReceiver(text)

    # autoComplete_singleで自動入力する対象を切り替える
    def switchMemorize(self):
        try:
            # completeTarget=
            # 0: ユーザID/Mail
            # 1:パスワード
            if self.cmdEvt.completeTarget == 0:
                self.cmdEvt.completeTarget = 1
                self.tray.setIcon(self.icon2)
            else:
                self.cmdEvt.completeTarget = 0
                self.tray.setIcon(self.icon)
            print(self.cmdEvt.completeTarget)
        except:
            traceback.print_exc()

    # 現在のmodeにおけるflag処理を1つ戻す
    def rollBack_at_flag(self):
        self.cmdEvt.shiftTabReceiver()

    # システムトレイ(通知領域)の設置
    def initSystemTray(self, tray):
        try:
            tray.setIcon(QIcon(self.resource_path(self.icon_path)))
            tray.setToolTip(self.tray_toolchipTxt)
            tray.show()
        except:
            traceback.print_exc()

    # コンソールに1文字入力される度に,その文字が使用可能か確認・修正する
    def checkConsoleText(self, text: str):
        if text == '':
            return
        txtList = list(text)
        newChar = txtList[-1]  # 最後の文字
        # パスワード入力かどうかで確認方法を変える
        if self.cmdEvt.currentFlag == self.cmdEvt.flag_PassWord:
            checked = self.passwordCheck(newChar)
        else:
            checked = self.newtralCheck(newChar)

        # 使用可能な文字だったら合格(何もしない)
        if checked:
            return

        # 使用不可能な文字なら消す
        del txtList[-1]
        fixedTxt = ''.join(txtList)
        self.changeConsoleText(fixedTxt)

    # 入力された文字がパスワードに使用可能か確認
    # True:使用可能
    # False:使用不可能
    def passwordCheck(self, newChar: str):
        checkTxt = 'abcdefghijklmnopqrstuvwxyz' \
                   'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
                   '1234567890#$%&_'
        checkList = list(checkTxt)
        # 使用可能な文字のどれかに当てはまれば合格
        for char in checkList:
            if char == newChar:
                return True
        return False

    # 入力された文字がサービス名などに使用可能か確認
    # True:使用可能
    # False:使用不可能
    # 今の所は何を入力してもOK
    def newtralCheck(self, text: str):
        return True

    # コンソールの入力内容を変更
    def changeConsoleText(self, text: str):
        self.console.setText(text)

    # コマンドによる処理結果を表示
    def dispResponseText(self, txt: str):
        try:
            self.responseTxt.setText(txt)
        except:
            traceback.print_exc()

    # コマンドを送る度に,現在のモードを表示
    def dispMyMode(self, mode: str):
        self.myMode.setText(mode)

    # アプリ終了処理
    def exitKeasy(self):
        self.tray.hide()  # 通知領域にアイコンだけ残らないようにする
        # 復号化された状態のDBを削除
        if os_path.exists(self.cmdEvt.keasy_path):
            os_remove(self.cmdEvt.keasy_path)
        sys.exit()

    # コンソールの入力文字を伏せる
    def setConsoleMode_password(self):
        self.console.setEchoMode(QLineEdit.Password)

    # コンソールの入力文字を表示する
    def setConsoleMode_raw(self):
        self.console.setEchoMode(QLineEdit.Normal)

    # アプリUI設定
    def initUI(self, MainFont: str, ConsoleFont: str):
        try:
            hline = QFrame()
            hline.setFrameShape(QFrame.HLine)
            vline = QFrame()
            vline.setFrameShape(QFrame.VLine)
            vline.setStyleSheet('color:rgb(0,200,0);')
            vline.setLineWidth(3)
            # 最上部========================================
            topBar = QHBoxLayout()
            icon = QPixmap(self.resource_path(self.icon_path)).scaled(30, 30)
            self.icon_area = QLabel()
            self.icon_area.setPixmap(icon)
            title = QLabel(self.appTitle + self.version)
            title.setStyleSheet('font-size:28px;')
            topBar.addWidget(self.icon_area)
            topBar.addWidget(title)
            topBar.addStretch()

            # 説明部========================================
            explainBar = QHBoxLayout()
            explainTxt = QLabel('exit : Keasyを終了します\n'
                                'find [検索ワード] : アカウントを検索します\n'
                                'memorize [サービス名] [ユーザID/Mail] : '
                                'IDとパスワードを一時記憶します.\n'
                                '       Shiftキーを2回押すことで自動入力できます\n'
                                'show : パスワード文字列を表示します\n'
                                'hide : パスワードを伏せ字(*)で隠します\n'
                                'add  : アカウントを登録します\n'
                                'edit [サービス名] [ユーザID/Mail] [変更したい情報の種類] : '
                                'アカウントを編集します\n'
                                'delete [サービス名] [ユーザID/Mail] : アカウントを削除します\n'
                                'master : マスターパスワードを変更します')
            explainTxt.setStyleSheet(MainFont)
            explainBar.addWidget(explainTxt)

            # 区切り========================================
            hSeparater = QHBoxLayout()
            hSeparater.addWidget(hline)
            vSeparater = QHBoxLayout()
            vSeparater.addWidget(vline)

            # コマンド履歴部======================================
            historyBar = QHBoxLayout()
            historyMark = QLabel('$ ')
            historyMark.setStyleSheet(MainFont)
            self.historyTxt = QLabel('aaa')
            self.historyTxt.setStyleSheet(MainFont)
            historyBar.addWidget(historyMark)
            historyBar.addWidget(self.historyTxt)
            historyBar.addStretch()

            # コンソール部======================================
            commandBar = QHBoxLayout()
            self.console = QLineEdit()
            self.console.setMaxLength(60)
            self.console.setStyleSheet(ConsoleFont + 'color:rgb(0,255,0);'
                                       'background:rgb(30,30,30);'
                                       'border-style:solid;'
                                       'border-width:1px;'
                                       'border-color:rgb(0,150,0);')
            # コマンドラインでEnterを押されたら,
            # 入力文字をreceiver関数に投げる
            # ※connectに直接関数を書いてもエラーを吐く
            #   無名関数lambdaとして書くと通る
            self.console.returnPressed.connect(
                lambda: self.cmdEvt.receiver(self.console.text()))
            self.console.textChanged.connect(
                lambda: self.checkConsoleText(self.console.text()))
            tellMaxInput = QLabel(self.command_maxLengthTxt)
            tellMaxInput.setStyleSheet(MainFont)
            tellMaxInput.setStyleSheet('font-size:16px;')
            self.myMode = QLabel()
            self.myMode.setStyleSheet(MainFont)
            dollar = QLabel('$')
            dollar.setStyleSheet(MainFont)
            commandBar.addWidget(self.myMode)
            commandBar.addWidget(dollar)
            commandBar.addWidget(self.console)
            commandBar.addWidget(tellMaxInput)

            # 応答部======================================
            responseBar = QHBoxLayout()
            responseMark = QLabel('>> ')
            responseMark.setAlignment(Qt.AlignTop)  # 上寄せ
            responseMark.setStyleSheet(MainFont)
            self.responseTxt = QLabel()
            self.responseTxt.setStyleSheet(MainFont)
            responseBar.addWidget(responseMark)
            responseBar.addWidget(self.responseTxt)
            responseBar.addStretch()

            # 表示部============================================
            resultArea = QHBoxLayout()
            self.resultTxt = QLabel()
            self.resultTxt.setStyleSheet(MainFont)

            # DB検索結果の表
            self.resultTable = QTableWidget()
            # Tabキーでフォーカスさせない
            self.resultTable.setFocusPolicy(Qt.NoFocus)
            # 行と列は後で追加する
            self.resultTable.setColumnCount(0)
            self.resultTable.setRowCount(0)
            # 1行目headerを無効化
            hHeader = self.resultTable.horizontalHeader()
            hHeader.hide()
            # 1列目headerを無効化
            vHeader = self.resultTable.verticalHeader()
            vHeader.hide()

            # 表のフォントサイズと枠線色
            self.resultTable.setStyleSheet(
                'font-size:14px; gridline-color:"#555";')
            # 表のダブルクリックを無効化
            self.resultTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
            # 表の範囲選択&選択時の色変更を無効化(選択セルのコピーは可能)
            self.resultTable.setSelectionMode(QAbstractItemView.NoSelection)
            # 表をクリックした場合の処理
            # self.resultTable.cellClicked.connect(self.myFunction)

            # 表の選択セルについて,文字と背景色を変更
            # tPalette = QPalette(self.resultTable.palette())
            # tPalette.setColor(QPalette.HighlightedText, QColor('#ccc'))
            # tPalette.setColor(QPalette.Highlight, QColor('#242'))
            # self.resultTable.setPalette(tPalette)  # 表に適用

            # resultArea.addWidget(self.resultTxt)
            resultArea.addWidget(self.resultTable)

            # レイアウト格納====================================
            mainLayout = QVBoxLayout()
            mainLayout.addLayout(topBar)
            mainLayout.addLayout(explainBar)
            mainLayout.addLayout(hSeparater)
            mainLayout.addLayout(historyBar)
            mainLayout.addLayout(commandBar)
            mainLayout.addLayout(responseBar)
            mainLayout.addLayout(hSeparater)
            mainLayout.addLayout(resultArea)
            # mainLayout.addStretch()  # 余白を埋める
            self.setLayout(mainLayout)
        except:
            traceback.print_exc()
Beispiel #31
0
class Main(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setIcon(QIcon('ui/icon.png'))
        self.trayIcon.activated.connect(self.restore_window)

        self.WM = WindowManager()
        self.pre_window = self.WM.get_fore_window()
        self.rnn = RNN()
        self.runState = False

        self.startButton.clicked.connect(self.btn_clk_start)
        self.startState = True
        self.trainButton.clicked.connect(self.btn_clk_train)
        self.helpButton.clicked.connect(self.btn_clk_help)
        self.helpState = True

        self.timer = QTimer(self)
        self.timer.start(200)
        self.timer.timeout.connect(self.run)

    def restore_window(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.trayIcon.hide()
            self.showNormal()

    def btn_clk_start(self):
        if self.startState:
            self.startState = False
            self.startButton.setText('Stop')

            self.runState = True
            self.back = Background()
            self.back.show()
            self.trayIcon.setVisible(True)
            self.hide()
        else:
            self.startState = True
            self.startButton.setText('Start')

            self.runState = False
            self.back.close()
            self.timer.stop()

    def btn_clk_train(self):
        if os.path.isfile('params.pkl'):
            os.remove('params.pkl')
        time.sleep(2)
        self.rnn = RNN()
        self.rnn.train()
        QMessageBox.information(self, "RNN", "train finished")

    def btn_clk_help(self):
        if self.helpState:
            self.helpState = False
            self.helpButton.setText('go to tray icon')
            QMessageBox.information(self, "Help", "*****@*****.**")
        else:
            self.helpState = True
            self.helpButton.setText('Help')
            self.trayIcon.setVisible(True)
            self.hide()

    def get_hash(self, text):
        hash = hashlib.md5()
        hash.update(text.encode())

        return hash.hexdigest()

    def run(self):
        try:
            with open("data.txt", 'a') as f:
                cur_window = self.WM.get_fore_window().split()[0]
                if cur_window != self.pre_window:
                    self.pre_window = cur_window
                    cur_window_hash = self.get_hash(cur_window)
                    f.write(" " + cur_window_hash)
                    if self.runState:
                        target_windows = list(
                            self.rnn.test([cur_window_hash], 3))
                        target_windows.append(cur_window_hash)
                        print(target_windows)
                        self.WM.set_window(self.WM.find_window('dimmer'))
                        all_windows = list(self.WM.get_windows())
                        for t_window in target_windows:
                            for i, window in enumerate(all_windows):
                                if t_window == self.get_hash(
                                        window.split()[0]):
                                    self.WM.set_window(
                                        self.WM.find_window(all_windows[i]))
        except:
            pass
Beispiel #32
0
class Gui(Ui_UI):
    def __init__(self):
        super(Gui, self).__init__()
        # self.init()
    def init(self):
        QApplication.setQuitOnLastWindowClosed(False)
        self.mainWin = QMainWindow()
        self.mainWin.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
        self.setupUi(self.mainWin)
        self.timer = self.QTimer
        # print("开始gui创建111")
        # self.mainWin.show()
        self.set_icon()
        self.set_menus()
        self.set_icon_menus()

    @property
    def QTimer(self):
        return QtCore.QTimer()

    def set_icon(self):
        self.icon = QSystemTrayIcon(self.mainWin)
        try:
            # icon_dir = Path("gui")
            # url = "{0}{2}{1}{3}{1}{4}{1}{5}".format(icon_dir.path["base"], icon_dir.path["sep"], "UI", "Data", "Assets", "icon.png")
            icon_dir = Path("Data")
            url = "{0}{2}{1}{3}{1}{4}".format(icon_dir.path["base"],
                                              icon_dir.path["sep"], "statics",
                                              "images", "icon.png")
            print(">>>>url", url)
            self.icon.setIcon(QIcon(url))
        except Exception as err:
            print("【err】【Gui ->set_icon】 icon图片文件错误")

    def set_menus(self):
        self.menu = {}
        self.menu["show"] = QAction('&显示(Show)', triggered=self.mainWin.show)
        self.menu["hide"] = QAction('&隐藏(Hide)', triggered=self.mainWin.hide)
        self.menu["exit"] = QAction('&退出(Exit)', triggered=self.exit)

        self.menus = QMenu()
        for act in self.menu:
            self.menus.addAction(self.menu[act])

    def set_icon_menus(self):
        self.icon.setContextMenu(self.menus)
        self.icon.activated.connect(self.click_btn)
        self.icon.show()
        print(">>>>show icon")

    def click_btn(self, reason):
        if reason == 2 or reason == 3:
            self.mainWin.show()

    def exit(self):
        re = QMessageBox.question(self.mainWin, "退出", "是否退出",
                                  QMessageBox.Yes | QMessageBox.No,
                                  QMessageBox.No)
        if re == QMessageBox.Yes:
            self.mainWin.hide()
            self.icon.hide()
            print("执行GUI退出功能!")
            hasattr(self, "exited") and self.exited()
            sys.exit(0)

    def restart(self):
        # if self.main_exit:
        #     self.main_exit()
        python = sys.executable
        os.execl(python, python, *sys.argv)

    def setExit(self, func):
        self.exited = func

    def __getitem__(self, item):
        return getattr(self, item, None)

    def clearText(self, objKey):
        # self[objKey].clear()
        self.links.clear()

    def setText(self, objKey, txt):
        Qobject = self[objKey]
        Qobject.setText(txt)

    def insertText(self, objKey, txt):
        Qobject = self[objKey]
        Qobject.insertPlainText(txt)
Beispiel #33
0
class XNova_MainWindow(QWidget):

    STATE_NOT_AUTHED = 0
    STATE_AUTHED = 1

    def __init__(self, parent=None):
        super(XNova_MainWindow, self).__init__(parent, Qt.Window)
        # state vars
        self.config_store_dir = './cache'
        self.cfg = configparser.ConfigParser()
        self.cfg.read('config/net.ini', encoding='utf-8')
        self.state = self.STATE_NOT_AUTHED
        self.login_email = ''
        self.cookies_dict = {}
        self._hidden_to_tray = False
        #
        # init UI
        self.setWindowIcon(QIcon(':/i/xnova_logo_64.png'))
        self.setWindowTitle('XNova Commander')
        # main layouts
        self._layout = QVBoxLayout()
        self._layout.setContentsMargins(0, 2, 0, 0)
        self._layout.setSpacing(3)
        self.setLayout(self._layout)
        self._horizontal_layout = QHBoxLayout()
        self._horizontal_layout.setContentsMargins(0, 0, 0, 0)
        self._horizontal_layout.setSpacing(6)
        # flights frame
        self._fr_flights = QFrame(self)
        self._fr_flights.setMinimumHeight(22)
        self._fr_flights.setFrameShape(QFrame.NoFrame)
        self._fr_flights.setFrameShadow(QFrame.Plain)
        # planets bar scrollarea
        self._sa_planets = QScrollArea(self)
        self._sa_planets.setMinimumWidth(125)
        self._sa_planets.setMaximumWidth(125)
        self._sa_planets.setFrameShape(QFrame.NoFrame)
        self._sa_planets.setFrameShadow(QFrame.Plain)
        self._sa_planets.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self._sa_planets.setWidgetResizable(True)
        self._panel_planets = QWidget(self._sa_planets)
        self._layout_pp = QVBoxLayout()
        self._panel_planets.setLayout(self._layout_pp)
        self._lbl_planets = QLabel(self.tr('Planets:'), self._panel_planets)
        self._lbl_planets.setMaximumHeight(32)
        self._layout_pp.addWidget(self._lbl_planets)
        self._layout_pp.addStretch()
        self._sa_planets.setWidget(self._panel_planets)
        #
        # tab widget
        self._tabwidget = XTabWidget(self)
        self._tabwidget.enableButtonAdd(False)
        self._tabwidget.tabCloseRequested.connect(self.on_tab_close_requested)
        self._tabwidget.addClicked.connect(self.on_tab_add_clicked)
        #
        # create status bar
        self._statusbar = XNCStatusBar(self)
        self.set_status_message(self.tr('Not connected: Log in!'))
        #
        # tab widget pages
        self.login_widget = None
        self.flights_widget = None
        self.overview_widget = None
        self.imperium_widget = None
        #
        # settings widget
        self.settings_widget = SettingsWidget(self)
        self.settings_widget.settings_changed.connect(self.on_settings_changed)
        self.settings_widget.hide()
        #
        # finalize layouts
        self._horizontal_layout.addWidget(self._sa_planets)
        self._horizontal_layout.addWidget(self._tabwidget)
        self._layout.addWidget(self._fr_flights)
        self._layout.addLayout(self._horizontal_layout)
        self._layout.addWidget(self._statusbar)
        #
        # system tray icon
        self.tray_icon = None
        show_tray_icon = False
        if 'tray' in self.cfg:
            if (self.cfg['tray']['icon_usage'] == 'show') or \
                    (self.cfg['tray']['icon_usage'] == 'show_min'):
                self.create_tray_icon()
        #
        # try to restore last window size
        ssz = self.load_cfg_val('main_size')
        if ssz is not None:
            self.resize(ssz[0], ssz[1])
        #
        # world initialization
        self.world = XNovaWorld_instance()
        self.world_timer = QTimer(self)
        self.world_timer.timeout.connect(self.on_world_timer)

    # overrides QWidget.closeEvent
    # cleanup just before the window close
    def closeEvent(self, close_event: QCloseEvent):
        logger.debug('closing')
        if self.tray_icon is not None:
            self.tray_icon.hide()
            self.tray_icon = None
        if self.world_timer.isActive():
            self.world_timer.stop()
        self.world.script_command = 'stop'  # also stop possible running scripts
        if self.world.isRunning():
            self.world.quit()
            logger.debug('waiting for world thread to stop (5 sec)...')
            wait_res = self.world.wait(5000)
            if not wait_res:
                logger.warn('wait failed, last chance, terminating!')
                self.world.terminate()
        # store window size
        ssz = (self.width(), self.height())
        self.store_cfg_val('main_size', ssz)
        # accept the event
        close_event.accept()

    def showEvent(self, evt: QShowEvent):
        super(XNova_MainWindow, self).showEvent(evt)
        self._hidden_to_tray = False

    def changeEvent(self, evt: QEvent):
        super(XNova_MainWindow, self).changeEvent(evt)
        if evt.type() == QEvent.WindowStateChange:
            if not isinstance(evt, QWindowStateChangeEvent):
                return
            # make sure we only do this for minimize events
            if (evt.oldState() != Qt.WindowMinimized) and self.isMinimized():
                # we were minimized! explicitly hide settings widget
                #     if it is open, otherwise it will be lost forever :(
                if self.settings_widget is not None:
                    if self.settings_widget.isVisible():
                        self.settings_widget.hide()
                # should we minimize to tray?
                if self.cfg['tray']['icon_usage'] == 'show_min':
                    if not self._hidden_to_tray:
                        self._hidden_to_tray = True
                        self.hide()

    def create_tray_icon(self):
        if QSystemTrayIcon.isSystemTrayAvailable():
            logger.debug('System tray icon is available, showing')
            self.tray_icon = QSystemTrayIcon(QIcon(':/i/xnova_logo_32.png'), self)
            self.tray_icon.setToolTip(self.tr('XNova Commander'))
            self.tray_icon.activated.connect(self.on_tray_icon_activated)
            self.tray_icon.show()
        else:
            self.tray_icon = None

    def hide_tray_icon(self):
        if self.tray_icon is not None:
            self.tray_icon.hide()
            self.tray_icon.deleteLater()
            self.tray_icon = None

    def set_tray_tooltip(self, tip: str):
        if self.tray_icon is not None:
            self.tray_icon.setToolTip(tip)

    def set_status_message(self, msg: str):
        self._statusbar.set_status(msg)

    def store_cfg_val(self, category: str, value):
        pickle_filename = '{0}/{1}.dat'.format(self.config_store_dir, category)
        try:
            cache_dir = pathlib.Path(self.config_store_dir)
            if not cache_dir.exists():
                cache_dir.mkdir()
            with open(pickle_filename, 'wb') as f:
                pickle.dump(value, f)
        except pickle.PickleError as pe:
            pass
        except IOError as ioe:
            pass

    def load_cfg_val(self, category: str, default_value=None):
        value = None
        pickle_filename = '{0}/{1}.dat'.format(self.config_store_dir, category)
        try:
            with open(pickle_filename, 'rb') as f:
                value = pickle.load(f)
                if value is None:
                    value = default_value
        except pickle.PickleError as pe:
            pass
        except IOError as ioe:
            pass
        return value

    @pyqtSlot()
    def on_settings_changed(self):
        self.cfg.read('config/net.ini', encoding='utf-8')
        # maybe show/hide tray icon now?
        show_tray_icon = False
        if 'tray' in self.cfg:
            icon_usage = self.cfg['tray']['icon_usage']
            if (icon_usage == 'show') or (icon_usage == 'show_min'):
                show_tray_icon = True
        # show if needs show and hidden, or hide if shown and needs to hide
        if show_tray_icon and (self.tray_icon is None):
            logger.debug('settings changed, showing tray icon')
            self.create_tray_icon()
        elif (not show_tray_icon) and (self.tray_icon is not None):
            logger.debug('settings changed, hiding tray icon')
            self.hide_tray_icon()
        # also notify world about changed config!
        self.world.reload_config()

    def add_tab(self, widget: QWidget, title: str, closeable: bool = True) -> int:
        tab_index = self._tabwidget.addTab(widget, title, closeable)
        return tab_index

    def remove_tab(self, index: int):
        self._tabwidget.removeTab(index)

    # called by main application object just after main window creation
    # to show login widget and begin login process
    def begin_login(self):
        # create flights widget
        self.flights_widget = FlightsWidget(self._fr_flights)
        self.flights_widget.load_ui()
        install_layout_for_widget(self._fr_flights, Qt.Vertical, margins=(1, 1, 1, 1), spacing=1)
        self._fr_flights.layout().addWidget(self.flights_widget)
        self.flights_widget.set_online_state(False)
        self.flights_widget.requestShowSettings.connect(self.on_show_settings)
        # create and show login widget as first tab
        self.login_widget = LoginWidget(self._tabwidget)
        self.login_widget.load_ui()
        self.login_widget.loginError.connect(self.on_login_error)
        self.login_widget.loginOk.connect(self.on_login_ok)
        self.login_widget.show()
        self.add_tab(self.login_widget, self.tr('Login'), closeable=False)
        # self.test_setup_planets_panel()
        # self.test_planet_tab()

    def setup_planets_panel(self, planets: list):
        layout = self._panel_planets.layout()
        layout.setSpacing(0)
        remove_trailing_spacer_from_layout(layout)
        # remove all previous planet widgets from planets panel
        if layout.count() > 0:
            for i in range(layout.count()-1, -1, -1):
                li = layout.itemAt(i)
                if li is not None:
                    wi = li.widget()
                    if wi is not None:
                        if isinstance(wi, PlanetSidebarWidget):
                            layout.removeWidget(wi)
                            wi.close()
                            wi.deleteLater()  # fix possible mem leak
                            del wi
        for pl in planets:
            pw = PlanetSidebarWidget(self._panel_planets)
            pw.setPlanet(pl)
            layout.addWidget(pw)
            pw.show()
            # connections from each planet bar widget
            pw.requestOpenGalaxy.connect(self.on_request_open_galaxy_tab)
            pw.requestOpenPlanet.connect(self.on_request_open_planet_tab)
        append_trailing_spacer_to_layout(layout)

    def update_planets_panel(self):
        """
        Calls QWidget.update() on every PlanetBarWidget
        embedded in ui.panel_planets, causing repaint
        """
        layout = self._panel_planets.layout()
        if layout.count() > 0:
            for i in range(layout.count()):
                li = layout.itemAt(i)
                if li is not None:
                    wi = li.widget()
                    if wi is not None:
                        if isinstance(wi, PlanetSidebarWidget):
                            wi.update()

    def add_tab_for_planet(self, planet: XNPlanet):
        # construct planet widget and setup signals/slots
        plw = PlanetWidget(self._tabwidget)
        plw.requestOpenGalaxy.connect(self.on_request_open_galaxy_tab)
        plw.setPlanet(planet)
        # construct tab title
        tab_title = '{0} {1}'.format(planet.name, planet.coords.coords_str())
        # add tab and make it current
        tab_index = self.add_tab(plw, tab_title, closeable=True)
        self._tabwidget.setCurrentIndex(tab_index)
        self._tabwidget.tabBar().setTabIcon(tab_index, QIcon(':/i/planet_32.png'))
        return tab_index

    def add_tab_for_galaxy(self, coords: XNCoords = None):
        gw = GalaxyWidget(self._tabwidget)
        tab_title = '{0}'.format(self.tr('Galaxy'))
        if coords is not None:
            tab_title = '{0} {1}'.format(self.tr('Galaxy'), coords.coords_str())
            gw.setCoords(coords.galaxy, coords.system)
        idx = self.add_tab(gw, tab_title, closeable=True)
        self._tabwidget.setCurrentIndex(idx)
        self._tabwidget.tabBar().setTabIcon(idx, QIcon(':/i/galaxy_32.png'))

    @pyqtSlot(int)
    def on_tab_close_requested(self, idx: int):
        # logger.debug('tab close requested: {0}'.format(idx))
        if idx <= 1:  # cannot close overview or imperium tabs
            return
        self.remove_tab(idx)

    @pyqtSlot()
    def on_tab_add_clicked(self):
        pos = QCursor.pos()
        planets = self.world.get_planets()
        # logger.debug('tab bar add clicked, cursor pos = ({0}, {1})'.format(pos.x(), pos.y()))
        menu = QMenu(self)
        # galaxy view
        galaxy_action = QAction(menu)
        galaxy_action.setText(self.tr('Add galaxy view'))
        galaxy_action.setData(QVariant('galaxy'))
        menu.addAction(galaxy_action)
        # planets
        menu.addSection(self.tr('-- Planet tabs: --'))
        for planet in planets:
            action = QAction(menu)
            action.setText('{0} {1}'.format(planet.name, planet.coords.coords_str()))
            action.setData(QVariant(planet.planet_id))
            menu.addAction(action)
        action_ret = menu.exec(pos)
        if action_ret is not None:
            # logger.debug('selected action data = {0}'.format(str(action_ret.data())))
            if action_ret == galaxy_action:
                logger.debug('action_ret == galaxy_action')
                self.add_tab_for_galaxy()
                return
            # else consider this is planet widget
            planet_id = int(action_ret.data())
            self.on_request_open_planet_tab(planet_id)

    @pyqtSlot(str)
    def on_login_error(self, errstr):
        logger.error('Login error: {0}'.format(errstr))
        self.state = self.STATE_NOT_AUTHED
        self.set_status_message(self.tr('Login error: {0}').format(errstr))
        QMessageBox.critical(self, self.tr('Login error:'), errstr)

    @pyqtSlot(str, dict)
    def on_login_ok(self, login_email, cookies_dict):
        # logger.debug('Login OK, login: {0}, cookies: {1}'.format(login_email, str(cookies_dict)))
        # save login data: email, cookies
        self.state = self.STATE_AUTHED
        self.set_status_message(self.tr('Login OK, loading world'))
        self.login_email = login_email
        self.cookies_dict = cookies_dict
        #
        # destroy login widget and remove its tab
        self.remove_tab(0)
        self.login_widget.close()
        self.login_widget.deleteLater()
        self.login_widget = None
        #
        # create overview widget and add it as first tab
        self.overview_widget = OverviewWidget(self._tabwidget)
        self.overview_widget.load_ui()
        self.add_tab(self.overview_widget, self.tr('Overview'), closeable=False)
        self.overview_widget.show()
        self.overview_widget.setEnabled(False)
        #
        # create 2nd tab - Imperium
        self.imperium_widget = ImperiumWidget(self._tabwidget)
        self.add_tab(self.imperium_widget, self.tr('Imperium'), closeable=False)
        self.imperium_widget.setEnabled(False)
        #
        # initialize XNova world updater
        self.world.initialize(cookies_dict)
        self.world.set_login_email(self.login_email)
        # connect signals from world
        self.world.world_load_progress.connect(self.on_world_load_progress)
        self.world.world_load_complete.connect(self.on_world_load_complete)
        self.world.net_request_started.connect(self.on_net_request_started)
        self.world.net_request_finished.connect(self.on_net_request_finished)
        self.world.flight_arrived.connect(self.on_flight_arrived)
        self.world.build_complete.connect(self.on_building_complete)
        self.world.loaded_overview.connect(self.on_loaded_overview)
        self.world.loaded_imperium.connect(self.on_loaded_imperium)
        self.world.loaded_planet.connect(self.on_loaded_planet)
        self.world.start()

    @pyqtSlot(str, int)
    def on_world_load_progress(self, comment: str, progress: int):
        self._statusbar.set_world_load_progress(comment, progress)

    @pyqtSlot()
    def on_world_load_complete(self):
        logger.debug('main: on_world_load_complete()')
        # enable adding new tabs
        self._tabwidget.enableButtonAdd(True)
        # update statusbar
        self._statusbar.set_world_load_progress('', -1)  # turn off progress display
        self.set_status_message(self.tr('World loaded.'))
        # update account info
        if self.overview_widget is not None:
            self.overview_widget.setEnabled(True)
            self.overview_widget.update_account_info()
            self.overview_widget.update_builds()
        # update flying fleets
        self.flights_widget.set_online_state(True)
        self.flights_widget.update_flights()
        # update planets
        planets = self.world.get_planets()
        self.setup_planets_panel(planets)
        if self.imperium_widget is not None:
            self.imperium_widget.setEnabled(True)
            self.imperium_widget.update_planets()
        # update statusbar
        self._statusbar.update_online_players_count()
        # update tray tooltip, add account name
        self.set_tray_tooltip(self.tr('XNova Commander') + ' - '
                              + self.world.get_account_info().login)
        # set timer to do every-second world recalculation
        self.world_timer.setInterval(1000)
        self.world_timer.setSingleShot(False)
        self.world_timer.start()

    @pyqtSlot()
    def on_loaded_overview(self):
        logger.debug('on_loaded_overview')
        # A lot of things are updated when overview is loaded
        #  * Account information and stats
        if self.overview_widget is not None:
            self.overview_widget.update_account_info()
        #  * flights will be updated every second anyway in on_world_timer(), so no need to call
        #    self.flights_widget.update_flights()
        #  * messages count also, is updated with flights
        #  * current planet may have changed
        self.update_planets_panel()
        #  * server time is updated also
        self._statusbar.update_online_players_count()

    @pyqtSlot()
    def on_loaded_imperium(self):
        logger.debug('on_loaded_imperium')
        # need to update imperium widget
        if self.imperium_widget is not None:
            self.imperium_widget.update_planets()
        # The important note here is that imperium update is the only place where
        # the planets list is read, so number of planets, their names, etc may change here
        # Also, imperium update OVERWRITES full planets array, so, all prev
        # references to planets in all GUI elements must be invalidated, because
        # they will point to unused, outdated planets
        planets = self.world.get_planets()
        # re-create planets sidebar
        self.setup_planets_panel(planets)
        # update all builds in overview widget
        if self.overview_widget:
            self.overview_widget.update_builds()
        # update all planet tabs with new planet references
        cnt = self._tabwidget.count()
        if cnt > 2:
            for index in range(2, cnt):
                tab_page = self._tabwidget.tabWidget(index)
                if tab_page is not None:
                    try:
                        tab_type = tab_page.get_tab_type()
                        if tab_type == 'planet':
                            tab_planet = tab_page.planet()
                            new_planet = self.world.get_planet(tab_planet.planet_id)
                            tab_page.setPlanet(new_planet)
                    except AttributeError:  # not all pages may have method get_tab_type()
                        pass

    @pyqtSlot(int)
    def on_loaded_planet(self, planet_id: int):
        logger.debug('Got signal on_loaded_planet({0}), updating overview '
                     'widget and planets panel'.format(planet_id))
        if self.overview_widget:
            self.overview_widget.update_builds()
        self.update_planets_panel()
        # update also planet tab, if any
        planet = self.world.get_planet(planet_id)
        if planet is not None:
            tab_idx = self.find_tab_for_planet(planet_id)
            if tab_idx != -1:
                tab_widget = self._tabwidget.tabWidget(tab_idx)
                if isinstance(tab_widget, PlanetWidget):
                    logger.debug('Updating planet tab #{}'.format(tab_idx))
                    tab_widget.setPlanet(planet)

    @pyqtSlot()
    def on_world_timer(self):
        if self.world:
            self.world.world_tick()
        self.update_planets_panel()
        if self.flights_widget:
            self.flights_widget.update_flights()
        if self.overview_widget:
            self.overview_widget.update_builds()
        if self.imperium_widget:
            self.imperium_widget.update_planet_resources()

    @pyqtSlot()
    def on_net_request_started(self):
        self._statusbar.set_loading_status(True)

    @pyqtSlot()
    def on_net_request_finished(self):
        self._statusbar.set_loading_status(False)

    @pyqtSlot(int)
    def on_tray_icon_activated(self, reason):
        # QSystemTrayIcon::Unknown	0	Unknown reason
        # QSystemTrayIcon::Context	1	The context menu for the system tray entry was requested
        # QSystemTrayIcon::DoubleClick	2	The system tray entry was double clicked
        # QSystemTrayIcon::Trigger	3	The system tray entry was clicked
        # QSystemTrayIcon::MiddleClick	4	The system tray entry was clicked with the middle mouse button
        if reason == QSystemTrayIcon.Trigger:
            # left-click
            self.setWindowState((self.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive)
            self.show()
            return

    def show_tray_message(self, title, message, icon_type=None, timeout_ms=None):
        """
        Shows message from system tray icon, if system supports it.
        If no support, this is just a no-op
        :param title: message title
        :param message: message text
        :param icon_type: one of:
        QSystemTrayIcon.NoIcon      0   No icon is shown.
        QSystemTrayIcon.Information 1   An information icon is shown.
        QSystemTrayIcon.Warning     2   A standard warning icon is shown.
        QSystemTrayIcon.Critical    3   A critical warning icon is shown
        """
        if self.tray_icon is None:
            return
        if self.tray_icon.supportsMessages():
            if icon_type is None:
                icon_type = QSystemTrayIcon.Information
            if timeout_ms is None:
                timeout_ms = 10000
            self.tray_icon.showMessage(title, message, icon_type, timeout_ms)
        else:
            logger.info('This system does not support tray icon messages.')

    @pyqtSlot()
    def on_show_settings(self):
        if self.settings_widget is not None:
            self.settings_widget.show()
            self.settings_widget.showNormal()

    @pyqtSlot(XNFlight)
    def on_flight_arrived(self, fl: XNFlight):
        logger.debug('main: flight arrival: {0}'.format(fl))
        mis_str = flight_mission_for_humans(fl.mission)
        if fl.direction == 'return':
            mis_str += ' ' + self.tr('return')
        short_fleet_info = self.tr('{0} {1} => {2}, {3} ship(s)').format(
            mis_str, fl.src, fl.dst, len(fl.ships))
        self.show_tray_message(self.tr('XNova: Fleet arrived'), short_fleet_info)

    @pyqtSlot(XNPlanet, XNPlanetBuildingItem)
    def on_building_complete(self, planet: XNPlanet, bitem: XNPlanetBuildingItem):
        logger.debug('main: build complete: on planet {0}: {1}'.format(
            planet.name, str(bitem)))
        # update also planet tab, if any
        if isinstance(planet, XNPlanet):
            tab_idx = self.find_tab_for_planet(planet.planet_id)
            if tab_idx != -1:
                tab_widget = self._tabwidget.tabWidget(tab_idx)
                if isinstance(tab_widget, PlanetWidget):
                    logger.debug('Updating planet tab #{}'.format(tab_idx))
                    tab_widget.setPlanet(planet)
        # construct message to show in tray
        if bitem.is_shipyard_item:
            binfo_str = '{0} x {1}'.format(bitem.quantity, bitem.name)
        else:
            binfo_str = self.tr('{0} lv.{1}').format(bitem.name, bitem.level)
        msg = self.tr('{0} has built {1}').format(planet.name, binfo_str)
        self.show_tray_message(self.tr('XNova: Building complete'), msg)

    @pyqtSlot(XNCoords)
    def on_request_open_galaxy_tab(self, coords: XNCoords):
        tab_index = self.find_tab_for_galaxy(coords.galaxy, coords.system)
        if tab_index == -1:  # create new tab for these coords
            self.add_tab_for_galaxy(coords)
            return
        # else switch to that tab
        self._tabwidget.setCurrentIndex(tab_index)

    @pyqtSlot(int)
    def on_request_open_planet_tab(self, planet_id: int):
        tab_index = self.find_tab_for_planet(planet_id)
        if tab_index == -1: # create new tab for planet
            planet = self.world.get_planet(planet_id)
            if planet is not None:
                self.add_tab_for_planet(planet)
                return
        # else switch to that tab
        self._tabwidget.setCurrentIndex(tab_index)

    def find_tab_for_planet(self, planet_id: int) -> int:
        """
        Finds tab index where specified planet is already opened
        :param planet_id: planet id to search for
        :return: tab index, or -1 if not found
        """
        cnt = self._tabwidget.count()
        if cnt < 3:
            return -1  # only overview and imperium tabs are present
        for index in range(2, cnt):
            tab_page = self._tabwidget.tabWidget(index)
            if tab_page is not None:
                try:
                    tab_type = tab_page.get_tab_type()
                    if tab_type == 'planet':
                        tab_planet = tab_page.planet()
                        if tab_planet.planet_id == planet_id:
                            # we have found tab index where this planet is already opened
                            return index
                except AttributeError:  # not all pages may have method get_tab_type()
                    pass
        return -1

    def find_tab_for_galaxy(self, galaxy: int, system: int) -> int:
        """
        Finds tab index where specified galaxy view is already opened
        :param galaxy: galaxy target coordinate
        :param system: system target coordinate
        :return: tab index, or -1 if not found
        """
        cnt = self._tabwidget.count()
        if cnt < 3:
            return -1  # only overview and imperium tabs are present
        for index in range(2, cnt):
            tab_page = self._tabwidget.tabWidget(index)
            if tab_page is not None:
                try:
                    tab_type = tab_page.get_tab_type()
                    if tab_type == 'galaxy':
                        coords = tab_page.coords()
                        if (coords[0] == galaxy) and (coords[1] == system):
                            # we have found galaxy tab index where this place is already opened
                            return index
                except AttributeError:  # not all pages may have method get_tab_type()
                    pass
        return -1

    def test_setup_planets_panel(self):
        """
        Testing only - add 'fictive' planets to test planets panel without loading data
        :return: None
        """
        pl1 = XNPlanet('Arnon', XNCoords(1, 7, 6))
        pl1.pic_url = 'skins/default/planeten/small/s_normaltempplanet08.jpg'
        pl1.fields_busy = 90
        pl1.fields_total = 167
        pl1.is_current = True
        pl2 = XNPlanet('Safizon', XNCoords(1, 232, 7))
        pl2.pic_url = 'skins/default/planeten/small/s_dschjungelplanet05.jpg'
        pl2.fields_busy = 84
        pl2.fields_total = 207
        pl2.is_current = False
        test_planets = [pl1, pl2]
        self.setup_planets_panel(test_planets)

    def test_planet_tab(self):
        """
        Testing only - add 'fictive' planet tab to test UI without loading world
        :return:
        """
        # construct planet
        pl1 = XNPlanet('Arnon', coords=XNCoords(1, 7, 6), planet_id=12345)
        pl1.pic_url = 'skins/default/planeten/small/s_normaltempplanet08.jpg'
        pl1.fields_busy = 90
        pl1.fields_total = 167
        pl1.is_current = True
        pl1.res_current.met = 10000000
        pl1.res_current.cry = 50000
        pl1.res_current.deit = 250000000  # 250 mil
        pl1.res_per_hour.met = 60000
        pl1.res_per_hour.cry = 30000
        pl1.res_per_hour.deit = 15000
        pl1.res_max_silos.met = 6000000
        pl1.res_max_silos.cry = 3000000
        pl1.res_max_silos.deit = 1000000
        pl1.energy.energy_left = 10
        pl1.energy.energy_total = 1962
        pl1.energy.charge_percent = 92
        # planet building item
        bitem = XNPlanetBuildingItem()
        bitem.gid = 1
        bitem.name = 'Рудник металла'
        bitem.level = 29
        bitem.remove_link = ''
        bitem.build_link = '?set=buildings&cmd=insert&building={0}'.format(bitem.gid)
        bitem.seconds_total = 23746
        bitem.cost_met = 7670042
        bitem.cost_cry = 1917510
        bitem.is_building_item = True
        # second bitem
        bitem2 = XNPlanetBuildingItem()
        bitem2.gid = 2
        bitem2.name = 'Рудник кристалла'
        bitem2.level = 26
        bitem2.remove_link = ''
        bitem2.build_link = '?set=buildings&cmd=insert&building={0}'.format(bitem2.gid)
        bitem2.seconds_total = 13746
        bitem2.cost_met = 9735556
        bitem2.cost_cry = 4667778
        bitem2.is_building_item = True
        bitem2.is_downgrade = True
        bitem2.seconds_left = bitem2.seconds_total // 2
        bitem2.calc_end_time()
        # add bitems
        pl1.buildings_items = [bitem, bitem2]
        # add
        self.add_tab_for_planet(pl1)
Beispiel #34
0
class SVApplication(QApplication):

    # Signals need to be on a QObject
    create_new_window_signal = pyqtSignal(str, object)
    cosigner_received_signal = pyqtSignal(object, object)
    labels_changed_signal = pyqtSignal(object)
    window_opened_signal = pyqtSignal(object)
    window_closed_signal = pyqtSignal(object)
    # Async tasks
    async_tasks_done = pyqtSignal()
    # Logging
    new_category = pyqtSignal(str)
    new_log = pyqtSignal(object)
    # Preferences updates
    fiat_ccy_changed = pyqtSignal()
    custom_fee_changed = pyqtSignal()
    op_return_enabled_changed = pyqtSignal()
    num_zeros_changed = pyqtSignal()
    base_unit_changed = pyqtSignal()
    fiat_history_changed = pyqtSignal()
    fiat_balance_changed = pyqtSignal()
    update_check_signal = pyqtSignal(bool, object)

    def __init__(self, argv):
        super().__init__(argv)
        self.windows = []
        self.log_handler = SVLogHandler()
        self.log_window = None
        self.net_dialog = None
        self.timer = QTimer()
        self.exception_hook = None
        # A floating point number, e.g. 129.1
        self.dpi = self.primaryScreen().physicalDotsPerInch()

        # init tray
        self.dark_icon = app_state.config.get("dark_icon", False)
        self.tray = QSystemTrayIcon(self._tray_icon(), None)
        self.tray.setToolTip('ElectrumSV')
        self.tray.activated.connect(self._tray_activated)
        self._build_tray_menu()
        self.tray.show()

        # FIXME
        set_language(app_state.config.get('language'))

        logs.add_handler(self.log_handler)
        self._start()

    def _start(self):
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum-sv.desktop')
        self.setWindowIcon(read_QIcon("electrum-sv.png"))
        self.installEventFilter(OpenFileEventFilter(self.windows))
        self.create_new_window_signal.connect(self.start_new_window)
        self.async_tasks_done.connect(app_state.async_.run_pending_callbacks)
        self.num_zeros_changed.connect(partial(self._signal_all, 'on_num_zeros_changed'))
        self.fiat_ccy_changed.connect(partial(self._signal_all, 'on_fiat_ccy_changed'))
        self.base_unit_changed.connect(partial(self._signal_all, 'on_base_unit_changed'))
        self.fiat_history_changed.connect(partial(self._signal_all, 'on_fiat_history_changed'))
        self.fiat_balance_changed.connect(partial(self._signal_all, 'on_fiat_balance_changed'))
        self.update_check_signal.connect(partial(self._signal_all, 'on_update_check'))
        ColorScheme.update_from_widget(QWidget())

    def _signal_all(self, method, *args):
        for window in self.windows:
            getattr(window, method)(*args)

    def _close(self):
        for window in self.windows:
            window.close()

    def close_window(self, window):
        app_state.daemon.stop_wallet_at_path(window.wallet.storage.path)
        self.windows.remove(window)
        self.window_closed_signal.emit(window)
        self._build_tray_menu()
        # save wallet path of last open window
        if not self.windows:
            app_state.config.save_last_wallet(window.wallet)
            self._last_window_closed()

    def _build_tray_menu(self):
        # Avoid immediate GC of old menu when window closed via its action
        if self.tray.contextMenu() is None:
            m = QMenu()
            self.tray.setContextMenu(m)
        else:
            m = self.tray.contextMenu()
            m.clear()
        for window in self.windows:
            submenu = m.addMenu(window.wallet.basename())
            submenu.addAction(_("Show/Hide"), window.show_or_hide)
            submenu.addAction(_("Close"), window.close)
        m.addAction(_("Dark/Light"), self._toggle_tray_icon)
        m.addSeparator()
        m.addAction(_("Exit ElectrumSV"), self._close)
        self.tray.setContextMenu(m)

    def _tray_icon(self):
        if self.dark_icon:
            return read_QIcon('electrumsv_dark_icon.png')
        else:
            return read_QIcon('electrumsv_light_icon.png')

    def _toggle_tray_icon(self):
        self.dark_icon = not self.dark_icon
        app_state.config.set_key("dark_icon", self.dark_icon, True)
        self.tray.setIcon(self._tray_icon())

    def _tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if all([w.is_hidden() for w in self.windows]):
                for w in self.windows:
                    w.bring_to_top()
            else:
                for w in self.windows:
                    w.hide()

    def new_window(self, path, uri=None):
        # Use a signal as can be called from daemon thread
        self.create_new_window_signal.emit(path, uri)

    def show_network_dialog(self, parent):
        if not app_state.daemon.network:
            parent.show_warning(_('You are using ElectrumSV in offline mode; restart '
                                  'ElectrumSV if you want to get connected'), title=_('Offline'))
            return
        if self.net_dialog:
            self.net_dialog.on_update()
            self.net_dialog.show()
            self.net_dialog.raise_()
            return
        self.net_dialog = NetworkDialog(app_state.daemon.network, app_state.config)
        self.net_dialog.show()

    def show_log_viewer(self):
        if self.log_window is None:
            self.log_window = SVLogWindow(None, self.log_handler)
        self.log_window.show()

    def _last_window_closed(self):
        for dialog in (self.net_dialog, self.log_window):
            if dialog:
                dialog.accept()

    def _maybe_choose_server(self):
        # Show network dialog if config does not exist
        if app_state.daemon.network and app_state.config.get('auto_connect') is None:
            try:
                wizard = InstallWizard(None)
                wizard.init_network(app_state.daemon.network)
                wizard.terminate()
            except Exception as e:
                if not isinstance(e, (UserCancelled, GoBack)):
                    logger.exception("")
                self.quit()

    def on_label_change(self, wallet: Abstract_Wallet, name: str, text: str) -> None:
        self.label_sync.set_label(wallet, name, text)

    def _create_window_for_wallet(self, wallet: Abstract_Wallet):
        if wallet.is_hardware_wallet():
            dialogs.show_named('hardware-wallet-quality')

        w = ElectrumWindow(wallet)
        self.windows.append(w)
        self._build_tray_menu()
        self.window_opened_signal.emit(w)
        return w

    def get_wallet_window(self, path: str) -> Optional[ElectrumWindow]:
        for w in self.windows:
            if w.wallet.storage.path == path:
                return w

    def start_new_window(self, path, uri, is_startup=False):
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it.'''
        for w in self.windows:
            if w.wallet.storage.path == path:
                w.bring_to_top()
                break
        else:
            try:
                wallet = app_state.daemon.load_wallet(path, None)
                if not wallet:
                    wizard = InstallWizard(None)
                    try:
                        if wizard.select_storage(path, is_startup=is_startup):
                            wallet = wizard.run_and_get_wallet()
                    except UserQuit:
                        pass
                    except UserCancelled:
                        pass
                    except GoBack as e:
                        logger.error('[start_new_window] Exception caught (GoBack) %s', e)
                    finally:
                        wizard.terminate()
                    if not wallet:
                        return
                    app_state.daemon.start_wallet(wallet)
            except Exception as e:
                logger.exception("")
                error_str = str(e)
                if '2fa' in error_str:
                    error_str = _('2FA wallets are not supported')
                msg = '\n'.join((_('Cannot load wallet "{}"').format(path), error_str))
                MessageBox.show_error(msg)
                return
            w = self._create_window_for_wallet(wallet)
        if uri:
            w.pay_to_URI(uri)
        w.bring_to_top()
        w.setWindowState(w.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)

        # this will activate the window
        w.activateWindow()
        return w

    def update_check(self) -> None:
        if (not app_state.config.get('check_updates', True) or
                app_state.config.get("offline", False)):
            return

        def f():
            import requests
            try:
                response = requests.request(
                    'GET', "https://electrumsv.io/release.json",
                    headers={'User-Agent' : 'ElectrumSV'}, timeout=10)
                result = response.json()
                self._on_update_check(True, result)
            except Exception:
                self._on_update_check(False, sys.exc_info())

        t = threading.Thread(target=f)
        t.setDaemon(True)
        t.start()

    def _on_update_check(self, success: bool, result: dict) -> None:
        if success:
            when_checked = datetime.datetime.now().astimezone().isoformat()
            app_state.config.set_key('last_update_check', result)
            app_state.config.set_key('last_update_check_time', when_checked, True)
        self.update_check_signal.emit(success, result)

    def initial_dialogs(self) -> None:
        '''Suppressible dialogs that are shown when first opening the app.'''
        dialogs.show_named('welcome-ESV-1.3.0a1')
        # This needs to be reworked or removed, as non-advanced users aren't sure whether
        # it is safe, and likely many people aren't quite sure if it should be done.
        # old_items = []
        # headers_path = os.path.join(app_state.config.path, 'blockchain_headers')
        # if os.path.exists(headers_path):
        #     old_items.append((_('the file "blockchain_headers"'), os.remove, headers_path))
        # forks_dir = os.path.join(app_state.config.path, 'forks')
        # if os.path.exists(forks_dir):
        #     old_items.append((_('the directory "forks/"'), shutil.rmtree, forks_dir))
        # if old_items:
        #     main_text = _('Delete the following obsolete items in <br>{}?'
        #                   .format(app_state.config.path))
        #     info_text = '<ul>{}</ul>'.format(''.join('<li>{}</li>'.format(text)
        #                                              for text, *rest in old_items))
        #     if dialogs.show_named('delete-obsolete-headers', main_text=main_text,
        #                           info_text=info_text):
        #         try:
        #             for _text, rm_func, *args in old_items:
        #                 rm_func(*args)
        #         except OSError as e:
        #             logger.exception('deleting obsolete files')
        #             dialogs.error_dialog(_('Error deleting files:'), info_text=str(e))

    def event_loop_started(self) -> None:
        self.cosigner_pool = CosignerPool()
        self.label_sync = LabelSync()
        if app_state.config.get("show_crash_reporter", default=True):
            self.exception_hook = Exception_Hook(self)
        self.timer.start()
        signal.signal(signal.SIGINT, lambda *args: self.quit())
        self.initial_dialogs()
        self._maybe_choose_server()
        app_state.config.open_last_wallet()
        path = app_state.config.get_wallet_path()
        if not self.start_new_window(path, app_state.config.get('url'), is_startup=True):
            self.quit()

    def run_app(self) -> None:
        when_started = datetime.datetime.now().astimezone().isoformat()
        app_state.config.set_key('previous_start_time', app_state.config.get("start_time"))
        app_state.config.set_key('start_time', when_started, True)
        self.update_check()

        threading.current_thread().setName('GUI')
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec
        self.timer.timeout.connect(app_state.device_manager.timeout_clients)

        QTimer.singleShot(0, self.event_loop_started)
        self.exec_()

        logs.remove_handler(self.log_handler)
        # Shut down the timer cleanly
        self.timer.stop()
        # clipboard persistence
        # see http://www.mail-archive.com/[email protected]/msg17328.html
        event = QtCore.QEvent(QtCore.QEvent.Clipboard)
        self.sendEvent(self.clipboard(), event)
        self.tray.hide()

    def run_coro(self, coro, *args, on_done=None):
        '''Run a coroutine.  on_done, if given, is passed the future containing the reuslt or
        exception, and is guaranteed to be called in the context of the GUI thread.
        '''
        def task_done(future):
            self.async_tasks_done.emit()

        future = app_state.async_.spawn(coro, *args, on_done=on_done)
        future.add_done_callback(task_done)
        return future

    def run_in_thread(self, func, *args, on_done=None):
        '''Run func(*args) in a thread.  on_done, if given, is passed the future containing the
        reuslt or exception, and is guaranteed to be called in the context of the GUI
        thread.
        '''
        return self.run_coro(run_in_thread, func, *args, on_done=on_done)
Beispiel #35
0
class MainWindow(QMainWindow):
	"""The main GUI application."""
	def __init__(self, config):
		"""Initializer for the GUI widgets. Pass in an instance of Config class, so that it may interact with the config."""
		super().__init__()

		self.config = config

		self.setWindowTitle("Livestreamer GUI v{}".format(APPVERSION))

		self.setup_systray()
		self.setup_menu()
		self.setup_geometry()

		self.livestreamer_thread = None
		self.thread_exit_grace_time = 10000 # How long a thread can take to exit in milliseconds
		self.timestamp_format = self.config.get_config_value("timestamp-format")

		self.setup_control_widgets()
		self.update_colors()

		# Load all streaming-related data
		self.selections = {"streamer": None, "channel": None}
		self.load_streamers()
		self.load_channels(self.streamer_input.currentText())
		
		# Do the first configuration, if the application was run for the first time
		self.do_init_config()

		# Finally show the window and the system tray icon, if it should be shown
		self.show()

		self.close_override = False
		self.show_hide_systray()

		self.check_and_do_database_migration()

	def do_init_config(self):
		do_config = self.config.get_config_value("is-configured")
		if do_config == 0:
			self.menu_cmd_configure()
			self.config.set_config_value("is-configured", 1)
		self.insertText("Using config database version '{}'".format(self.config.get_config_value("db-version")))

	def setup_systray(self):
		if not self.config.get_config_value("enable-systray-icon"):
			self.systray = None
			return

		self.systray = QSystemTrayIcon(self)
		self.systray.activated.connect(self.systray_activated)
		main_menu = QMenu(self)

		quit_action = QAction("&Quit", self)
		quit_action.triggered.connect(self.on_close_override)
		main_menu.addAction(quit_action)

		self.systray.setContextMenu(main_menu)

	def systray_activated(self, reason):
		if reason == QSystemTrayIcon.Trigger:
			if self.isVisible():
				self.hide()
			else:
				self.showNormal()

	def check_and_do_database_migration(self):
		current_version = self.config.get_config_value("db-version")
		if self.config.is_migration_needed():
			self.insertText("Detected pending config database upgrade to version '{}'. Awaiting user input...".format(DBVERSION))
			message = "You are using an older version of the application config database.\n\nWould you like to upgrade the database now? Your existing config database will be backed up."

			upgrade_is_mandatory = current_version < MANDATORY_DBVERSION

			if upgrade_is_mandatory:
				message = message + "\n\nWARNING: Your config database is not compatible with this version of Livestreamer GUI. UPDATE IS MANDATORY! If you cancel the update, the application will exit."

			reply = QMessageBox.question(self, "Pending config database upgrade", message, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
			if reply == QMessageBox.Yes:
				self.insertText("Backing up config database...")
				backup = self.config.make_database_backup()
				self.insertText("Current config database backed up to '{}'".format(backup))
				self.insertText("Config database update initialized...")
				self.update()
				self.config.execute_migration()
				new_version = self.config.get_config_value("db-version")
				self.insertText("Config database update from version '{}' to '{}' finished.".format(current_version, new_version))
			elif reply == QMessageBox.No and upgrade_is_mandatory:
				QtCore.QTimer.singleShot(500, self.on_close_override)
				# self.on_close_override() # Calling this in an __init__()-called method doesn't seem to work...
			else:
				self.insertText("Config database update cancelled. No changes were made.")

	def setup_menu(self):
		config_action = QAction("&Configure...", self)
		config_action.triggered.connect(self.menu_cmd_configure)

		quit_action = QAction("&Quit", self)
		quit_action.setShortcut("Ctrl+Q")
		quit_action.triggered.connect(self.on_close_override)

		menu = self.menuBar()
		file_menu = menu.addMenu("&File")
		file_menu.addAction(config_action)
		file_menu.addSeparator()
		file_menu.addAction(quit_action)

	def setup_geometry(self):
		width = self.config.get_config_value("root-width")
		height = self.config.get_config_value("root-height")

		topleft = QApplication.desktop().availableGeometry().topLeft()
		if self.config.get_config_value("remember-window-position"):
			xoffset = self.config.get_config_value("root-xoffset")
			yoffset = self.config.get_config_value("root-yoffset")
			topleft.setX(self.config.get_config_value("root-xoffset"))
			topleft.setY(self.config.get_config_value("root-yoffset"))

		self.resize(width, height)
		self.setMinimumSize(500, 300)
		self.move(topleft)

		# Center the window
		# center_point = QApplication.desktop().availableGeometry().center()
		# frame_geometry = self.frameGeometry()
		# frame_geometry.moveCenter(center_point)
		# self.move(frame_geometry.topLeft())

	def setup_control_widgets(self):
		self.cwidget = QWidget(self)
		self.setCentralWidget(self.cwidget)

		layout = QGridLayout(self.cwidget)
		self.cwidget.setLayout(layout)

		fg_fav = self.config.get_config_value("button-foreground-favorite")
		fg_edit = self.config.get_config_value("button-foreground-edit")
		fg_add = self.config.get_config_value("button-foreground-add")
		fg_delete = self.config.get_config_value("button-foreground-delete")

		control_button_width = 30
		control_button_font_style = "QPushButton { font-family: Arial, sans-serif; font-size: 16px }"

		column = 0
		label_streamer_input = QLabel("Streamer", self.cwidget)
		layout.addWidget(label_streamer_input, 0, column)
		label_channel_input = QLabel("Channel", self.cwidget)
		layout.addWidget(label_channel_input, 1, column)
		label_quality_input = QLabel("Stream quality", self.cwidget)
		layout.addWidget(label_quality_input, 2, column)

		column += 1
		self.streamer_input = QComboBox(self.cwidget)
		self.streamer_input.setEnabled(False)
		self.streamer_input.currentIndexChanged.connect(self.on_streamer_select)
		layout.addWidget(self.streamer_input, 0, column)
		self.channel_input = QComboBox(self.cwidget)
		self.channel_input.setEnabled(False)
		self.channel_input.currentIndexChanged.connect(self.on_channel_select)
		layout.addWidget(self.channel_input, 1, column)
		self.quality_input = QComboBox(self.cwidget)
		self.quality_input.addItem("(auto-refresh is disabled; please refresh manually)")
		self.quality_input.setEnabled(False)
		layout.addWidget(self.quality_input, 2, column)
		layout.setColumnStretch(column, 5)

		column += 1
		self.fav_streamer_button = QPushButton("\u2764", self.cwidget)
		self.fav_streamer_button.setMaximumWidth(control_button_width)
		self.fav_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_fav, control_button_font_style))
		self.fav_streamer_button.setEnabled(False)
		self.fav_streamer_button.setToolTip("Set the selected streamer as your most favorite streamer")
		self.fav_streamer_button.clicked.connect(self.cmd_set_favorite_streamer)
		layout.addWidget(self.fav_streamer_button, 0, column)
		self.fav_channel_button = QPushButton("\u2764", self.cwidget)
		self.fav_channel_button.setMaximumWidth(control_button_width)
		self.fav_channel_button.setStyleSheet(':enabled {{ color: {0} }} {1}'.format(fg_fav, control_button_font_style))
		self.fav_channel_button.setEnabled(False)
		self.fav_channel_button.setToolTip("Set the selected channel as your most favorite channel")
		self.fav_channel_button.clicked.connect(self.cmd_set_favorite_channel)
		layout.addWidget(self.fav_channel_button, 1, column)
		self.clear_quality_cache_button = QPushButton("Refresh streams", self.cwidget)
		self.clear_quality_cache_button.setEnabled(False)
		self.clear_quality_cache_button.clicked.connect(self.cmd_refresh_quality_cache)
		layout.addWidget(self.clear_quality_cache_button, 2, column, 1, 4)

		column += 1
		self.edit_streamer_button = QPushButton("\u270E", self.cwidget)
		self.edit_streamer_button.setMaximumWidth(control_button_width)
		self.edit_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_edit, control_button_font_style))
		self.edit_streamer_button.setEnabled(False)
		self.edit_streamer_button.setToolTip("Edit data about the selected streamer")
		self.edit_streamer_button.clicked.connect(self.cmd_edit_streamer)
		layout.addWidget(self.edit_streamer_button, 0, column)
		self.edit_channel_button = QPushButton("\u270E", self.cwidget)
		self.edit_channel_button.setMaximumWidth(control_button_width)
		self.edit_channel_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_edit, control_button_font_style))
		self.edit_channel_button.setToolTip("Edit data about the selected channel")
		self.edit_channel_button.clicked.connect(self.cmd_edit_channel)
		layout.addWidget(self.edit_channel_button, 1, column)

		column += 1
		self.add_streamer_button = QPushButton("\u271A", self.cwidget)
		self.add_streamer_button.setMaximumWidth(control_button_width)
		self.add_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_add, control_button_font_style))
		self.add_streamer_button.setEnabled(False)
		self.add_streamer_button.setToolTip("Add a new streamer")
		self.add_streamer_button.clicked.connect(self.cmd_add_streamer)
		layout.addWidget(self.add_streamer_button, 0, column)
		self.add_channel_button = QPushButton("\u271A", self.cwidget)
		self.add_channel_button.setMaximumWidth(control_button_width)
		self.add_channel_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_add, control_button_font_style))
		self.add_channel_button.setToolTip("Add a new channel")
		self.add_channel_button.clicked.connect(self.cmd_add_channel)
		layout.addWidget(self.add_channel_button, 1, column)

		column += 1
		self.delete_streamer_button = QPushButton("\u2716", self.cwidget)
		self.delete_streamer_button.setMaximumWidth(control_button_width)
		self.delete_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_delete, control_button_font_style))
		self.delete_streamer_button.setEnabled(False)
		self.delete_streamer_button.setToolTip("Remove the selected streamer permanently")
		self.delete_streamer_button.clicked.connect(self.cmd_delete_streamer)
		layout.addWidget(self.delete_streamer_button, 0, column)
		self.delete_channel_button = QPushButton("\u2716", self.cwidget)
		self.delete_channel_button.setMaximumWidth(control_button_width)
		self.delete_channel_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_delete, control_button_font_style))
		self.delete_channel_button.setToolTip("Remove the selected channel permanently")
		self.delete_channel_button.clicked.connect(self.cmd_delete_channel)
		layout.addWidget(self.delete_channel_button, 1, column)

		# Add button for running livestreamer at the fourth row
		self.run_livestreamer_button = QPushButton("Run Livestreamer", self.cwidget)
		self.run_livestreamer_button.setEnabled(False)
		self.run_livestreamer_button.clicked.connect(self.run_livestreamer)
		layout.addWidget(self.run_livestreamer_button, 3, 0)

		self.log_widget = QTextEdit(self.cwidget)
		layout.addWidget(self.log_widget, 4, 0, 1, column+1)
		self.log_widget.setAcceptRichText(False)
		self.log_widget.setReadOnly(True)
		self.log_widget.setTabChangesFocus(True)

	def set_window_icon(self):
		"""Sets the root window's icon, which is also shown in the taskbar."""
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		icon = QIcon(os.path.join(IMAGESROOT, streamer["icon"]))
		self.setWindowIcon(icon)

		if self.systray is not None:
			self.systray.setIcon(icon)

	def closeEvent(self, event):
		"""When the QWidget is closed, QCloseEvent is triggered, and this method catches and handles it."""
		if not self.close_override and self.put_to_systray("close"):
			event.ignore()
			return

		if self.livestreamer_thread is not None and self.livestreamer_thread.keep_running:
			reply = QMessageBox.question(self, "Really quit Livestreamer GUI?", "Livestreamer is still running. Quitting will close it and the opened player.\n\nQuit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
			if reply == QMessageBox.Yes:
				# Terminate the child process, else it'll keep running even after this application is closed
				if self.livestreamer_thread is not None:
					self.livestreamer_thread.term_process()
					self.livestreamer_thread.wait(self.thread_exit_grace_time)
					self.update()
				event.accept()
			else:
				event.ignore()

		# Explicitly hide the icon, if it remains visible after the application closes
		if self.systray is not None:
			self.systray.hide()

		# Remember the position of the window
		self.remember_window_position()

		event.accept()

	def changeEvent(self, event):
		if type(event) is not QWindowStateChangeEvent:
			return

		# It's one of the window state change events (normal, minimize, maximize, fullscreen, active)
		if self.isMinimized():
			self.put_to_systray("minimize")

	def remember_window_position(self):
		if self.config.get_config_value("remember-window-position"):
			point = self.frameGeometry().topLeft()
			self.config.set_config_value("root-xoffset", point.x())
			self.config.set_config_value("root-yoffset", point.y())
			self.insertText("Window position saved.")

	def show_hide_systray(self):
		if self.systray is None:
			self.setup_systray()
			if self.systray is None:
				return

		if self.config.get_config_value("enable-systray-icon"):
			self.systray.show()
		else:
			self.systray.hide()

	def put_to_systray(self, event):
		if event == "minimize":
			config_value = "minimize-to-systray"
		elif event == "close":
			config_value = "close-to-systray"
		else:
			return False

		if self.systray is not None and self.config.get_config_value(config_value) and self.isVisible():
			self.hide()
			return True
		return False

	def menu_cmd_configure(self):
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		dialog = AppConfigDialog(self, self.config, streamer_icon=os.path.join(IMAGESROOT, streamer["icon"]))
		dialog.exec()
		if dialog.result() == QDialog.Accepted:
			self.show_hide_systray()
			self.update_colors()
		dialog.close()
		dialog = None

	def cmd_set_favorite_streamer(self):
		raise NotImplementedException()
		# self.fav_streamer_button.setEnabled(False)
		# self.config.set_favorite_streamer(self.streamer_input.setCurrentText())
		# self.insertText("Favorited streamer '{}'.".format(self.streamer_input.setCurrentText()))

	def cmd_edit_streamer(self):
		raise NotImplementedException()

	def cmd_add_streamer(self):
		raise NotImplementedException()

	def cmd_delete_streamer(self):
		raise NotImplementedException()

	def cmd_set_favorite_channel(self):
		self.fav_channel_button.setEnabled(False)
		self.config.set_favorite_channel(self.streamer_input.currentText(), self.channel_input.currentText())
		self.insertText("Favorited channel '{}'.".format(self.channel_input.currentText()))

	def cmd_edit_channel(self):
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		streamer_icon = os.path.join(IMAGESROOT, streamer["icon"])
		channel_data = self.config.get_streamer_channel(streamer["name"], self.channel_input.currentText())
		dialog = AddEditChannelsDialog(self, self.config, title="Edit the channel", streamer_icon=streamer_icon, streamer=streamer, channel_data=channel_data)
		dialog.exec()
		result = dialog.result_data
		dialog.close()
		dialog = None
		if result is not None:
			self.insertText("Updated channel name '{old_name}' => '{new_name}, URL '{old_url}' => '{new_url}'".format(old_name=channel_data["name"], new_name=result["name"], old_url=channel_data["url"], new_url=result["url"]))
			self.load_channels(streamer["name"])
			
			# Set the active channel to the previously selected (due to possible name change and sorting)
			self.channel_input.setCurrentIndex(self.channel_input.findText(result["name"]))
			
	def cmd_add_channel(self):
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		streamer_icon = os.path.join(IMAGESROOT, streamer["icon"])
		dialog = AddEditChannelsDialog(self, self.config, title="Add a channel", streamer_icon=streamer_icon, streamer=streamer)
		dialog.exec()
		result = dialog.result_data
		dialog.close()
		dialog = None
		if result is not None:
			self.insertText("Added channel '{}' with URL '{}'".format(result["name"], result["url"]))
			self.load_channels(streamer["name"])

	def cmd_delete_channel(self):
		channel = self.config.get_streamer_channel(self.streamer_input.currentText(), self.channel_input.currentText())
		reply = QMessageBox.question(self, "Delete channel", "Are you sure you want to remove the channel?\nName: {}\nURL: {}".format(channel["name"], channel["url"]), QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
		if reply == QMessageBox.Yes:
			self.config.delete_channel(self.streamer_input.currentText(), channel["name"])
			self.insertText("Removed channel '{}' with URL '{}'".format(channel["name"], channel["url"]))
			self.load_channels(self.streamer_input.currentText())

	def cmd_refresh_quality_cache(self):
		self.insertText("Refreshing cache for channel '{}'.".format(self.channel_input.currentText()))
		self.clear_quality_cache_button.setEnabled(False)
		self.clear_quality_cache_button.repaint() # Loading streams seems to block repainting of the GUI, so force a repaint here
		self.config.clean_quality_cache(self.streamer_input.currentText(), self.channel_input.currentText(), True)
		self.load_streams(True)
		self.clear_quality_cache_button.setEnabled(True)

	def on_close_override(self):
		self.close_override = True
		self.close()

	def on_streamer_select(self, event):
		# If the previously selected item is selected again, don't do anything
		if self.selections["streamer"] == self.streamer_input.currentText():
			return
		self.selections["streamer"] = self.streamer_input.currentText()
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		self.set_window_icon()
		if streamer["favorite"]:
			self.fav_streamer_button.setEnabled(False)
		else:
			self.fav_streamer_button.setEnabled(True)

	def on_channel_select(self, event):
		# If the previously selected item is selected again, don't do anything
		if self.selections["channel"] == self.channel_input.currentText() or not self.channel_input.currentText():
			return
		self.selections["channel"] = self.channel_input.currentText()
		channel = self.config.get_streamer_channel(self.streamer_input.currentText(), self.channel_input.currentText())
		if channel and channel["favorite"]:
			self.fav_channel_button.setEnabled(False)
		else:
			self.fav_channel_button.setEnabled(True)

		self.load_streams()
		self.channel_input.setFocus(True)

	def load_streamers(self):
		streamers = self.config.get_streamers()
		favorite_streamer_index = 0
		streamer_list = []
		for index, streamer in enumerate(streamers):
			streamer_list.append(streamer["name"])
			if streamer["favorite"]:
				favorite_streamer_index = index
		self.streamer_input.clear()
		self.streamer_input.addItems(streamer_list)
		if len(streamer_list) != 0:
			self.streamer_input.setCurrentIndex(favorite_streamer_index)
		self.selections["streamer"] = self.streamer_input.currentText()
		self.fav_streamer_button.setEnabled(False)

	def load_channels(self, streamer_name):
		channels = self.config.get_streamer_channels(streamer_name)
		self.channel_input.clear()
		favorite_channel = None
		channel_list = []
		self.fav_channel_button.setEnabled(False)
		for index, channel in enumerate(channels):
			channel_list.append(channel["name"])
			if channel["favorite"]:
				favorite_channel = channel["name"]
		self.channel_input.addItems(sorted(channel_list))
		if len(channel_list) == 0:
			self.channel_input.addItem("(no channels exist for this streamer)")
			self.fav_channel_button.setEnabled(False)
			self.edit_channel_button.setEnabled(False)
			self.delete_channel_button.setEnabled(False)
			self.clear_quality_cache_button.setEnabled(False)
			self.channel_input.setEnabled(False)
		else:
			self.edit_channel_button.setEnabled(True)
			self.delete_channel_button.setEnabled(True)
			self.clear_quality_cache_button.setEnabled(True)
			self.channel_input.setEnabled(True)
			
			if favorite_channel is None:
				self.channel_input.setCurrentIndex(0)
				self.fav_channel_button.setEnabled(True)
			else:
				self.channel_input.setCurrentIndex(self.channel_input.findText(favorite_channel))

		self.selections["channel"] = self.channel_input.currentText()

	def display_loaded_streams(self, streams, skip_caching=False):
		self.quality_input.clear()
		if len(streams) == 0:
			self.quality_input.addItem("(channel is currently not streaming)")
		else:
			self.run_livestreamer_button.setEnabled(True)
			self.clear_quality_cache_button.setEnabled(True)
			self.quality_input.addItems(sorted(streams))
			self.quality_input.setCurrentIndex(0)
			self.quality_input.setEnabled(True)
			if not skip_caching:
				self.insertText("Cleaning any cached streams for channel '{}'...".format(self.channel_input.currentText()))
				self.config.clean_quality_cache(self.streamer_input.currentText(), self.channel_input.currentText())
				self.insertText("Adding probed streams for channel '{}' to cache...".format(self.channel_input.currentText()))
				self.config.add_quality_to_cache(self.streamer_input.currentText(), self.channel_input.currentText(), streams)
				self.insertText("Done.")

	def load_streams(self, force_refresh=False):
		self.quality_input.clear()
		self.run_livestreamer_button.setEnabled(False)
		self.channel_input.setEnabled(False)
		self.quality_input.setEnabled(False)

		if self.channel_input.count() == 0:
			return

		streams = self.config.get_quality_from_cache(self.streamer_input.currentText(), self.channel_input.currentText())
		if len(streams) > 0:
			self.display_loaded_streams(streams, True)
			self.insertText("Loaded streams for channel '{}' from cache.".format(self.channel_input.currentText()))
		else:
			self.insertText("No cached channel streams found for channel '{}'".format(self.channel_input.currentText()))
			if not force_refresh and self.config.get_config_value('auto-refresh-quality') == 0:
				self.quality_input.addItem("(auto-refresh is disabled; please refresh manually)")
				self.quality_input.setEnabled(False)
			else:
				stream_url = self.get_streamer_url()
				if stream_url is None:
					self.insertText("Failed to form a complete streamer URL (missing streamer/channel/stream)!")
					return
				self.probe_for_streams(stream_url)
		
		self.channel_input.setEnabled(True)

	def probe_for_streams(self, stream_url):
		self.insertText("Probing streamer's channel for live streams: {}".format(stream_url))
		livestreamer = self.config.get_config_value("livestreamer-path")
		if livestreamer is None or livestreamer.strip() == "" or not os.path.isfile(livestreamer):
			self.insertText("Livestreamer path is not configured or file doesn't exist!")
			return
		command_format = self.config.get_config_value("probe-command-format")
		command = command_format.format(livestreamer=livestreamer, url=stream_url)
		self.livestreamer_thread = LivestreamerWorker(shlex.split(command))
		self.livestreamer_thread.statusMessage.connect(self.parse_probed_streams, False)
		self.livestreamer_thread.start()
		self.livestreamer_thread.wait(self.thread_exit_grace_time)

	def parse_probed_streams(self, event):
		streams = []

		message = event.message.lower()
		if "no streams found on this url" in message:
			self.insertText("No streams found. The channel is probably not streaming.")
		else:
			pos = message.find("available streams:")
			if pos == -1:
				return

			if "(best, worst)" in message:
				message = message.replace("(best, worst)", "(best and worst)")
			elif "(worst, best)" in message:
				message = message.replace("(worst, best)", "(worst and best)")
			qualities = message[pos+18:].split(",")
			for item in qualities:
				streams.append(item.strip())
				left_parenthesis = item.find("(")
				if left_parenthesis == -1:
					continue
				if item.find("worst", left_parenthesis) >= left_parenthesis:
					streams.append("worst")
				if item.find("best", left_parenthesis) >= left_parenthesis:
					streams.append("best")
			streams.sort()
			self.insertText("Found {} stream(s): {}".format(len(streams), ", ".join(streams)))

		self.display_loaded_streams(streams)

	def get_streamer_url(self):
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		if streamer is None:
			self.insertText("No streamer selected!")
			return
		if streamer["url"] is None or streamer["url"].strip() == "":
			self.insertText("Invalid streamer URL!")
			return
		if self.channel_input.count() == 0:
			self.insertText("No channels exist!")
			return
		channel = self.config.get_streamer_channel(streamer["name"], self.channel_input.currentText())
		return urljoin(streamer["url"], channel["url"])

	def run_livestreamer(self):
		if self.livestreamer_thread is not None:
			if self.livestreamer_thread.isRunning():
				self.insertText("Livestreamer should still be running!")
				return
			else:
				self.livestreamer_thread.wait(self.thread_exit_grace_time)
				self.livestreamer_thread = None
				self.update()

		if self.livestreamer_thread is None:
			livestreamer = self.config.get_config_value("livestreamer-path")
			if livestreamer is None or livestreamer.strip() == "" or not os.path.isfile(livestreamer):
				self.insertText("Livestreamer path is not configured or file doesn't exist!")
				return
			player = self.config.get_config_value("player-path")
			if player is None or player.strip() == "" or not os.path.isfile(player):
				self.insertText("Player path is not configured or file doesn't exist!")
				return
			stream_url = self.get_streamer_url()
			if stream_url is None:
				self.insertText("Failed to form a complete streamer URL (missing streamer/channel/stream)!")
				return
			command_format = self.config.get_config_value("command-format")
			quality = self.quality_input.currentText()
			if "(" in quality:
				quality = quality[:quality.find("(")].strip()
			command = command_format.format(livestreamer=livestreamer, player=player, url=stream_url, quality=quality)
			self.livestreamer_thread = LivestreamerWorker(shlex.split(command))
			self.insertText("Starting Livestreamer thread.")
			self.livestreamer_thread.finished.connect(self.handle_livestreamer_thread_finished_signal)
			self.livestreamer_thread.statusMessage.connect(self.handle_livestreamer_thread_message_signal)
			self.livestreamer_thread.start()

	@QtCore.pyqtSlot(object)
	def handle_livestreamer_thread_message_signal(self, event):
		self.insertText(event.message, event.add_newline, event.add_timestamp)

	def handle_livestreamer_thread_finished_signal(self):
		self.livestreamer_thread = None

	def update_colors(self):
		foreground_color = self.config.get_config_value("foreground-color")
		background_color = self.config.get_config_value("background-color")
		self.cwidget.setStyleSheet("QWidget QLabel {{ color: {0} }} .QWidget {{ background-color: {1} }}".format(foreground_color, background_color))
		self.cwidget.update()

	def insertText(self, msg, add_newline=True, timestamp=True):
		"""Helper method for outputting text to the text box."""
		text = ""
		if timestamp and self.timestamp_format is not None:
			timestamp = format(datetime.now().strftime(self.timestamp_format))
			text = "{} ".format(timestamp)
		text += msg
		self.log_widget.moveCursor(QTextCursor.End)
		self.log_widget.insertPlainText(text)
		if add_newline:
			self.log_widget.insertPlainText("\n")
		self.log_widget.update()
Beispiel #36
0
class TrayIcon(QObject):

    def __init__(self):
        super().__init__()
        self._supported = QSystemTrayIcon.isSystemTrayAvailable()
        self._context_menu = None
        self._enabled = False
        self._trayicon = None
        self._state_icons = {}
        for state in (
            'disconnected',
            'disabled',
            'enabled',
        ):
            icon = QIcon(':/state-%s.svg' % state)
            if hasattr(icon, 'setIsMask'):
                icon.setIsMask(True)
            self._state_icons[state] = icon
        self._machine = None
        self._machine_state = 'disconnected'
        self._is_running = False
        self._update_state()

    def set_menu(self, menu):
        self._context_menu = menu
        if self._enabled:
            self._trayicon.setContextMenu(menu)

    def log(self, level, message):
        if self._enabled:
            if level <= log.INFO:
                icon = QSystemTrayIcon.Information
                timeout = 10
            elif level <= log.WARNING:
                icon = QSystemTrayIcon.Warning
                timeout = 15
            else:
                icon = QSystemTrayIcon.Critical
                timeout = 25
            self._trayicon.showMessage(__software_name__.capitalize(),
                                       message, icon, timeout * 1000)
        else:
            if level <= log.INFO:
                icon = QMessageBox.Information
            elif level <= log.WARNING:
                icon = QMessageBox.Warning
            else:
                icon = QMessageBox.Critical
            msgbox = QMessageBox()
            msgbox.setText(message)
            msgbox.setIcon(icon)
            msgbox.exec_()

    def is_supported(self):
        return self._supported

    def enable(self):
        if not self._supported:
            return
        self._trayicon = QSystemTrayIcon()
        # On OS X, the context menu is activated with either mouse buttons,
        # and activation messages are still sent, so ignore those...
        if PLATFORM != 'mac':
            self._trayicon.activated.connect(self._on_activated)
        if self._context_menu is not None:
            self._trayicon.setContextMenu(self._context_menu)
        self._enabled = True
        self._update_state()
        self._trayicon.show()

    def disable(self):
        if not self._enabled:
            return
        self._trayicon.hide()
        self._trayicon = None
        self._enabled = False

    def is_enabled(self):
        return self._enabled

    def update_machine_state(self, machine, state):
        self._machine = machine
        self._machine_state = state
        self._update_state()

    def update_output(self, enabled):
        self._is_running = enabled
        self._update_state()

    clicked = pyqtSignal()

    def _update_state(self):
        if self._machine_state not in (STATE_INITIALIZING, STATE_RUNNING):
            state = 'disconnected'
        else:
            state = 'enabled' if self._is_running else 'disabled'
        icon = self._state_icons[state]
        if not self._enabled:
            return
        machine_state = _('{machine} is {state}').format(
            machine=_(self._machine),
            state=_(self._machine_state),
        )
        if self._is_running:
            # i18n: Tray icon tooltip.
            output_state = _('output is enabled')
        else:
            # i18n: Tray icon tooltip.
            output_state = _('output is disabled')
        self._trayicon.setIcon(icon)
        self._trayicon.setToolTip(
            # i18n: Tray icon tooltip.
            'Plover:\n- %s\n- %s' % (output_state, machine_state)
        )

    def _on_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.clicked.emit()
Beispiel #37
0
class MainWindow(Window):
    tray = None
    isMain = True
    updater = None

    def __init__(self, isMain=True, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.isMain = isMain
        self.showWindowTitle = Util.getConfigValue('showTitle')
        self.showWindowIcon = Util.getConfigValue('showWindowIcon')
        self.defaultStyle = Util.getConfigValue('style')
        self.closeConfirm = Util.getConfigValue('closeConfirm')
        self.closeToTray = Util.getConfigValue('closeToTray')

        if not self.isMain:
            self.titleBar.buttonMore.hide()
        if not self.showWindowIcon:
            self.titleBar.iconLabel.hide()
        if not self.showWindowTitle:
            self.titleBar.titleLabel.hide()

        self.titleBar.settionAction.triggered.disconnect()
        self.titleBar.settionAction.triggered.connect(self.on_moreMenu_setting)
        self.titleBar.exitAction.triggered.disconnect()
        self.titleBar.exitAction.triggered.connect(self.on_moreMenu_exit)
        self.titleBar.settingLabel.setText(Util.lang('setting', 'Setting'))
        self.titleBar.exitLabel.setText(Util.lang('exit', 'Exit'))

        #updater
        self.updater = Updater()
        self.updater.signal.connect(self.log)
        self.updater.init()

    def on_moreMenu_setting(self):
        from setting import DlgSetting

        self.settingDialog = DlgSetting(self)

        self.settingWindow = Window()
        self.settingWindow.setWindowTitle(Util.lang('setting', 'Setting'))
        self.settingWindow.setWindowIcon(Icon.getLogoIcon())
        self.settingWindow.setWidget(self.settingDialog)
        self.settingWindow.titleBar.buttonMinimum.hide()
        self.settingWindow.titleBar.buttonMaximum.hide()
        self.settingWindow.titleBar.buttonMore.hide()
        self.settingWindow.resizeCenter(500, 420)
        self.settingWindow.showPopupWindow()

    def reloadConfig(self):
        Util.reloadConfig()
        self.showWindowTitle = Util.getConfigValue('showTitle')
        self.showWindowIcon = Util.getConfigValue('showWindowIcon')
        self.defaultStyle = Util.getConfigValue('style')
        self.closeConfirm = Util.getConfigValue('closeConfirm')
        self.closeToTray = Util.getConfigValue('closeToTray')
        Util.loadTheme()

    def on_settingOK(self):
        old_style = self.defaultStyle
        self.reloadConfig()

        if self.showWindowIcon:
            self.titleBar.iconLabel.show()
        else:
            self.titleBar.iconLabel.hide()

        if self.showWindowTitle:
            self.titleBar.titleLabel.show()
        else:
            self.titleBar.titleLabel.hide()

        #change theme
        new_style = Util.getConfigValue('style')
        if old_style != new_style:
            self._widget.webview.page().runJavaScript(
                "changeBrowserTheme('%s')" % new_style)

    def on_moreMenu_exit(self):
        if self.closeConfirm and self.isMain:
            reply = MessageBox.question(
                self.parentWidget(), Util.lang('msg_title', 'Information'),
                Util.lang('exit_confirm', 'Are you sure to exit?'),
                QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.exitAll()
        else:
            self.exitAll()

    def closeEvent(self, event):
        if self.closeToTray and self.isMain:
            self.hide()
            event.ignore()
        elif self.closeConfirm and self.isMain:
            reply = MessageBox.question(
                self.parentWidget(), Util.lang('msg_title', 'Information'),
                Util.lang('exit_confirm', 'Are you sure to exit?'),
                QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.exitAll()
            else:
                event.ignore()
        elif self.isMain:
            self.exitAll()
        else:
            event.accept()

    def addSystemTray(self):
        self.tray = QSystemTrayIcon()

        self.icon = Icon.getLogoIcon()
        self.tray.setIcon(self.icon)

        self.tray.activated.connect(self.clickTray)
        #self.tray.messageClicked.connect(self.clickTray)

        self.tray_menu = QMenu(QApplication.desktop())
        self.ShowAction = QAction(Util.lang('show_window', 'Window'),
                                  self,
                                  triggered=self.showWindow)
        self.SettingAction = QAction(Util.lang('setting', 'Setting'),
                                     self,
                                     triggered=self.on_moreMenu_setting)
        self.QuitAction = QAction(Util.lang('exit', 'Exit'),
                                  self,
                                  triggered=self.on_moreMenu_exit)
        self.tray_menu.addAction(self.ShowAction)
        self.tray_menu.addAction(self.SettingAction)
        self.tray_menu.addAction(self.QuitAction)

        self.tray.setContextMenu(self.tray_menu)

        self.tray.show()

    def clickTray(self, reason):
        if reason != QSystemTrayIcon.DoubleClick:
            return False

        self.showWindow()

    def showWindow(self):
        if self.isMaximized():
            self.showMaximized()
        elif self.isFullScreen():
            self.showFullScreen()
        else:
            self.showNormal()

        self.activateWindow()

    def loadData(self, splash=None):
        self._widget.loadUrl(Util.getConfigValue('url'))

        if Util.isWindows():
            self.addSystemTray()

        #check upgrade
        mainScript = Util.getScriptName()
        t1 = threading.Thread(target=self.updater.checkUpgrade,
                              args=(False, mainScript))
        t1.setDaemon(True)
        t1.start()

        #splash
        if splash:
            splash.showMessage("Loading... 100%",
                               Qt.AlignHCenter | Qt.AlignBottom, Qt.white)
            qApp.processEvents()

    def log(self, o):
        if o['extra']:
            if o['extra'][0] == 'check-end' and o['extra'][1]['allowUpgrade']:
                reply = MessageBox.question(
                    self.parentWidget(), Util.lang('msg_title', 'Information'),
                    Util.lang(
                        'allow_upgrade',
                        'There is a newer version, do you want to download it?'
                    ), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
                if reply == QMessageBox.Yes:
                    mainScript = Util.getScriptName()
                    self.updater.runScriptFile(self.updater.updaterFilePath,
                                               ' -s' + mainScript)
                    sys.exit()

    def exitAll(self):
        self.tray and self.tray.hide()
        sys.exit()

    def __del__(self):
        #print('close')
        self.tray and self.tray.hide()
Beispiel #38
0
class UI_Handler:
    def __init__(self, ui, MainWindow, config):
        # Initialize input value
        self.ui = ui
        self.timer_enabled = False
        self.random_movement_enabled = False
        self.random_delay_enabled = False
        self.tray_minimize_enabled = False
        self.mouse_handler = Mouse_Handler()
        self.ui_input = UI_Input(config)

        # Iinitialize minimize to tray handler
        self.MainWindow = MainWindow
        self.MainWindow.closeEvent = self.closeEvent
        self.tray_icon = QSystemTrayIcon(self.MainWindow)
        self.tray_icon.setIcon(QIcon('resource/256x256.png'))
        show_action = QAction("Restore", self.MainWindow)
        quit_action = QAction("Exit", self.MainWindow)
        show_action.triggered.connect(self.MainWindow.show)
        quit_action.triggered.connect(qApp.quit)
        quit_action.triggered.connect(self.tray_icon.hide)
        tray_menu = QMenu()
        tray_menu.addAction(show_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()
        self.tray_icon.activated.connect(self.MainWindow.show)

    def enable_timer(self, element):
        # Enable timer
        if element.isChecked():
            self.timer_enabled = True
            self.ui.timerHour.setEnabled(True)
            self.ui.timerMinute.setEnabled(True)
            self.ui.timerSecond.setEnabled(True)
        else:
            self.timer_enabled = False
            self.ui.timerHour.setEnabled(False)
            self.ui.timerMinute.setEnabled(False)
            self.ui.timerSecond.setEnabled(False)

        self.ui_input.timer_enabled = self.timer_enabled

    def enable_random_movement(self, element):
        # Enable random movement
        if element.isChecked():
            self.random_movement_enabled = True
        else:
            self.random_movement_enabled = False

        self.ui_input.random_movement_enabled = self.random_movement_enabled

    def enable_random_delay(self, element):
        # Enable random delay interval between each movement
        if element.isChecked():
            self.random_delay_enabled = True
        else:
            self.random_delay_enabled = False

        self.ui_input.random_delay_enabled = self.random_delay_enabled

    def enable_tray_minimize(self, element):
        # Enable the program to be minimize to system tray
        if element.isChecked():
            self.tray_minimize_enabled = True
        else:
            self.tray_minimize_enabled = False

    def start_mouse_movement(self, element):
        # Start mouse movement
        element.setEnabled(False)
        self.ui.stopButton.setEnabled(True)
        self.enable_settings_input(False)

        self.mouse_handler.start_mouse_movement(self.ui_input)

        if self.timer_enabled:
            self.get_timer_values()
            self.start_timer()

    def stop_mouse_movement(self):
        # Stop mouse movement
        self.ui.stopButton.setEnabled(False)
        self.ui.startButton.setEnabled(True)
        self.enable_settings_input(True)

        self.mouse_handler.stop_mouse_movement()
        self.ui.currentTimerValue.setText("00:00:00")

    def start_timer(self):
        # Convert the timer inputs to seconds
        total_time = (self.ui_input.timer_Hour * 60 * 60) + \
            (self.ui_input.timer_Minute * 60) + (self.ui_input.timer_Second)
        self.mouse_handler.start_timer(total_time, self)

    def enable_settings_input(self, state):
        """
        Enable or disable all the settings input
        This will be call when the user press "Start" or "Stop" button
        """
        self.ui.timerEnabled.setEnabled(state)
        if state is True:
            if self.ui.timerEnabled.isChecked():
                self.ui.timerHour.setEnabled(True)
                self.ui.timerMinute.setEnabled(True)
                self.ui.timerSecond.setEnabled(True)
            else:
                self.ui.timerHour.setEnabled(False)
                self.ui.timerMinute.setEnabled(False)
                self.ui.timerSecond.setEnabled(False)
        else:
            self.ui.timerHour.setEnabled(state)
            self.ui.timerMinute.setEnabled(state)
            self.ui.timerSecond.setEnabled(state)
        self.ui.randomMovementEnabled.setEnabled(state)
        self.ui.randomDelayEnabled.setEnabled(state)

    def get_timer_values(self):
        # Get the input value from the timer field
        if (self.ui.timerHour.text() != ''):
            self.ui_input.timer_Hour = int(self.ui.timerHour.text())
        else:
            self.ui_input.timer_Hour = 0

        if (self.ui.timerMinute.text() != ''):
            self.ui_input.timer_Minute = int(self.ui.timerMinute.text())
        else:
            self.ui_input.timer_Minute = 0

        if (self.ui.timerSecond.text() != ''):
            self.ui_input.timer_Second = int(self.ui.timerSecond.text())
        else:
            self.ui_input.timer_Second = 0

    def closeEvent(self, event):
        # Will trigger when user pressed close button on the UI window
        if self.tray_minimize_enabled:
            event.ignore()
            self.MainWindow.hide()
            # self.tray_icon.showMessage(
            #     "Mouse Mover",
            #     "Minimized to tray",
            #     QSystemTrayIcon.Information,
            #     2000
            # )
        else:
            self.stop_mouse_movement()
            self.tray_icon.hide()
Beispiel #39
0
class UI_Handler:

    def __init__(self, ui, MainWindow, config):
        # Initialize input value
        self.ui = ui
        self.timer_enabled = False
        self.random_movement_enabled = False
        self.random_delay_enabled = False
        self.tray_minimize_enabled = False
        self.mouse_handler = Mouse_Handler()
        self.ui_input = UI_Input(config)

        # Iinitialize minimize to tray handler
        self.MainWindow = MainWindow
        self.MainWindow.closeEvent = self.closeEvent
        self.tray_icon = QSystemTrayIcon(self.MainWindow)
        self.tray_icon.setIcon(QIcon('resource/256x256.png'))
        show_action = QAction("Restore", self.MainWindow)
        quit_action = QAction("Exit", self.MainWindow)
        show_action.triggered.connect(self.MainWindow.show)
        quit_action.triggered.connect(qApp.quit)
        quit_action.triggered.connect(self.tray_icon.hide)
        tray_menu = QMenu()
        tray_menu.addAction(show_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()
        self.tray_icon.activated.connect(self.MainWindow.show)

    def enable_timer(self, element):
        if element.isChecked():
            self.timer_enabled = True
            self.ui.timerHour.setEnabled(True)
            self.ui.timerMinute.setEnabled(True)
            self.ui.timerSecond.setEnabled(True)
        else:
            self.timer_enabled = False
            self.ui.timerHour.setEnabled(False)
            self.ui.timerMinute.setEnabled(False)
            self.ui.timerSecond.setEnabled(False)

        self.ui_input.timer_enabled  = self.timer_enabled
    
    def enable_random_movement(self, element):
        if element.isChecked():
            self.random_movement_enabled = True
        else:
            self.random_movement_enabled = False
        
        self.ui_input.random_movement_enabled  = self.random_movement_enabled

    def enable_random_delay(self, element):
        if element.isChecked():
            self.random_delay_enabled = True
        else:
            self.random_delay_enabled = False
        
        self.ui_input.random_delay_enabled  = self.random_delay_enabled

    def enable_tray_minimize(self, element):
        if element.isChecked():
            self.tray_minimize_enabled = True
        else:
            self.tray_minimize_enabled = False
        
    def start_mouse_movement(self, element):
        element.setEnabled(False)
        self.ui.stopButton.setEnabled(True)
        self.enable_settings_input(False)

        self.mouse_handler.start_mouse_movement(self.ui_input)

        if self.timer_enabled:
            self.get_timer_values()
            self.start_timer()
    
    def stop_mouse_movement(self):
        self.ui.stopButton.setEnabled(False)
        self.ui.startButton.setEnabled(True)
        self.enable_settings_input(True)

        self.mouse_handler.stop_mouse_movement()
    
    def start_timer(self):
        total_time = (self.ui_input.timer_Hour * 60 * 60) + (self.ui_input.timer_Minute * 60) + (self.ui_input.timer_Second)
        self.mouse_handler.start_timer(total_time, self)
    
    def enable_settings_input(self, state):
        self.ui.timerEnabled.setEnabled(state)
        if state == True:
            if self.ui.timerEnabled.isChecked():
                self.ui.timerHour.setEnabled(True)
                self.ui.timerMinute.setEnabled(True)
                self.ui.timerSecond.setEnabled(True)
            else:
                self.ui.timerHour.setEnabled(False)
                self.ui.timerMinute.setEnabled(False)
                self.ui.timerSecond.setEnabled(False)
        else:
            self.ui.timerHour.setEnabled(state)
            self.ui.timerMinute.setEnabled(state)
            self.ui.timerSecond.setEnabled(state)
        self.ui.randomMovementEnabled.setEnabled(state)
        self.ui.randomDelayEnabled.setEnabled(state)
    
    def get_timer_values(self):
        if(self.ui.timerHour.text() != ''):
            self.ui_input.timer_Hour = int(self.ui.timerHour.text())
        else:
            self.ui_input.timer_Hour = 0
        
        if(self.ui.timerMinute.text() != ''):
            self.ui_input.timer_Minute = int(self.ui.timerMinute.text())
        else:
            self.ui_input.timer_Minute = 0
        
        if(self.ui.timerSecond.text() != ''):
            self.ui_input.timer_Second = int(self.ui.timerSecond.text())
        else:
            self.ui_input.timer_Second = 0
    
    def closeEvent(self, event):
        if self.tray_minimize_enabled:
            event.ignore()
            self.MainWindow.hide()
            # self.tray_icon.showMessage(
            #     "Mouse Mover",
            #     "Minimized to tray",
            #     QSystemTrayIcon.Information,
            #     2000
            # )
        else:
            self.stop_mouse_movement()
            self.tray_icon.hide()
class ElectrumGui(Logger):

    network_dialog: Optional['NetworkDialog']

    @profiler
    def __init__(self, config: 'SimpleConfig', daemon: 'Daemon',
                 plugins: 'Plugins'):
        set_language(config.get('language', get_default_language()))
        Logger.__init__(self)
        self.logger.info(
            f"Qt GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}"
        )
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(
                QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum-dash.desktop')
        self.gui_thread = threading.current_thread()
        self.config = config
        self.daemon = daemon
        self.plugins = plugins
        self.windows = []  # type: List[ElectrumWindow]
        self.efilter = OpenFileEventFilter(self.windows)
        self.app = QElectrumApplication(sys.argv)
        self.app.installEventFilter(self.efilter)
        self.app.setWindowIcon(read_QIcon("electrum-dash.png"))
        self._cleaned_up = False
        # timer
        self.timer = QTimer(self.app)
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec

        self.network_dialog = None
        self.dash_net_dialog = None
        self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
        self.dash_net_sobj = QDashNetSignalsObject()
        self._num_wizards_in_progress = 0
        self._num_wizards_lock = threading.Lock()
        self.dark_icon = self.config.get("dark_icon", False)
        self.tray = None
        self._init_tray()
        self.app.new_window_signal.connect(self.start_new_window)
        self.set_dark_theme_if_needed()
        run_hook('init_qt', self)

    def _init_tray(self):
        self.tray = QSystemTrayIcon(self.tray_icon(), None)
        self.tray.setToolTip('Dash Electrum')
        self.tray.activated.connect(self.tray_activated)
        self.build_tray_menu()
        self.tray.show()

    def set_dark_theme_if_needed(self):
        use_dark_theme = self.config.get('qt_gui_color_theme',
                                         'default') == 'dark'
        self.app.setStyle('Fusion')
        if use_dark_theme:
            from .dark_dash_style import dash_stylesheet
            self.app.setStyleSheet(dash_stylesheet)
        else:
            from .dash_style import dash_stylesheet
            self.app.setStyleSheet(dash_stylesheet)
        # Apply any necessary stylesheet patches
        patch_qt_stylesheet(use_dark_theme=use_dark_theme)
        # Even if we ourselves don't set the dark theme,
        # the OS/window manager/etc might set *a dark theme*.
        # Hence, try to choose colors accordingly:
        ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)

    def build_tray_menu(self):
        if not self.tray:
            return
        # Avoid immediate GC of old menu when window closed via its action
        if self.tray.contextMenu() is None:
            m = QMenu()
            self.tray.setContextMenu(m)
        else:
            m = self.tray.contextMenu()
            m.clear()
        m.addAction(_("Network"), self.show_network_dialog)
        for window in self.windows:
            name = window.wallet.basename()
            submenu = m.addMenu(name)
            submenu.addAction(_("Show/Hide"), window.show_or_hide)
            submenu.addAction(_("Close"), window.close)
        m.addAction(_("Dark/Light"), self.toggle_tray_icon)
        m.addSeparator()
        m.addAction(_("Exit Dash Electrum"), self.app.quit)

    def tray_icon(self):
        if self.dark_icon:
            return read_QIcon('electrum_dark_icon.png')
        else:
            return read_QIcon('electrum_light_icon.png')

    def toggle_tray_icon(self):
        if not self.tray:
            return
        self.dark_icon = not self.dark_icon
        self.config.set_key("dark_icon", self.dark_icon, True)
        self.tray.setIcon(self.tray_icon())

    def tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if all([w.is_hidden() for w in self.windows]):
                for w in self.windows:
                    w.bring_to_top()
            else:
                for w in self.windows:
                    w.hide()

    def _cleanup_before_exit(self):
        if self._cleaned_up:
            return
        self._cleaned_up = True
        self.app.new_window_signal.disconnect()
        self.efilter = None
        # If there are still some open windows, try to clean them up.
        for window in list(self.windows):
            window.close()
            window.clean_up()
        if self.network_dialog:
            self.network_dialog.close()
            self.network_dialog.clean_up()
            self.network_dialog = None
        self.network_updated_signal_obj = None
        if self.dash_net_dialog:
            self.dash_net_dialog.close()
            self.dash_net_dialog.clean_up()
            self.dash_net_dialog = None
        self.dash_net_sobj = None
        # Shut down the timer cleanly
        self.timer.stop()
        self.timer = None
        # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html
        event = QtCore.QEvent(QtCore.QEvent.Clipboard)
        self.app.sendEvent(self.app.clipboard(), event)
        if self.tray:
            self.tray.hide()
            self.tray.deleteLater()
            self.tray = None

    def _maybe_quit_if_no_windows_open(self) -> None:
        """Check if there are any open windows and decide whether we should quit."""
        # keep daemon running after close
        if self.config.get('daemon'):
            return
        # check if a wizard is in progress
        with self._num_wizards_lock:
            if self._num_wizards_in_progress > 0 or len(self.windows) > 0:
                return
        self.app.quit()

    def new_window(self, path, uri=None):
        # Use a signal as can be called from daemon thread
        self.app.new_window_signal.emit(path, uri)

    def show_network_dialog(self):
        if self.network_dialog:
            self.network_dialog.on_update()
            self.network_dialog.show()
            self.network_dialog.raise_()
            return
        self.network_dialog = NetworkDialog(
            network=self.daemon.network,
            config=self.config,
            network_updated_signal_obj=self.network_updated_signal_obj)
        self.network_dialog.show()

    def show_dash_net_dialog(self):
        if self.dash_net_dialog:
            self.dash_net_dialog.on_updated()
            self.dash_net_dialog.show()
            self.dash_net_dialog.raise_()
            return
        self.dash_net_dialog = DashNetDialog(network=self.daemon.network,
                                             config=self.config,
                                             dash_net_sobj=self.dash_net_sobj)
        self.dash_net_dialog.show()

    def _create_window_for_wallet(self, wallet):
        w = ElectrumWindow(self, wallet)
        self.windows.append(w)
        self.build_tray_menu()
        w.warn_if_testnet()
        w.warn_if_watching_only()
        return w

    def count_wizards_in_progress(func):
        def wrapper(self: 'ElectrumGui', *args, **kwargs):
            with self._num_wizards_lock:
                self._num_wizards_in_progress += 1
            try:
                return func(self, *args, **kwargs)
            finally:
                with self._num_wizards_lock:
                    self._num_wizards_in_progress -= 1
                self._maybe_quit_if_no_windows_open()

        return wrapper

    @count_wizards_in_progress
    def start_new_window(self,
                         path,
                         uri,
                         *,
                         app_is_starting=False) -> Optional[ElectrumWindow]:
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it'''
        wallet = None
        if self.config.get('tor_auto_on', True):
            network = self.daemon.network
            if network:
                proxy_modifiable = self.config.is_modifiable('proxy')
                if not proxy_modifiable or not network.detect_tor_proxy():
                    warn_d = TorWarnDialog(self, path)
                    warn_d.exec_()
                    if warn_d.result() < 0:
                        return
        try:
            wallet = self.daemon.load_wallet(path, None)
        except Exception as e:
            self.logger.exception('')
            custom_message_box(icon=QMessageBox.Warning,
                               parent=None,
                               title=_('Error'),
                               text=_('Cannot load wallet') + ' (1):\n' +
                               repr(e))
            # if app is starting, still let wizard to appear
            if not app_is_starting:
                return
        if not wallet:
            try:
                wallet = self._start_wizard_to_select_or_create_wallet(path)
            except (WalletFileException, BitcoinException) as e:
                self.logger.exception('')
                custom_message_box(icon=QMessageBox.Warning,
                                   parent=None,
                                   title=_('Error'),
                                   text=_('Cannot load wallet') + ' (2):\n' +
                                   repr(e))
        if not wallet:
            return
        # create or raise window
        try:
            for window in self.windows:
                if window.wallet.storage.path == wallet.storage.path:
                    break
            else:
                window = self._create_window_for_wallet(wallet)
        except Exception as e:
            self.logger.exception('')
            custom_message_box(icon=QMessageBox.Warning,
                               parent=None,
                               title=_('Error'),
                               text=_('Cannot create window for wallet') +
                               ':\n' + repr(e))
            if app_is_starting:
                wallet_dir = os.path.dirname(path)
                path = os.path.join(wallet_dir,
                                    get_new_wallet_name(wallet_dir))
                self.start_new_window(path, uri)
            return
        if uri:
            window.pay_to_URI(uri)
        window.bring_to_top()
        window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized
                              | QtCore.Qt.WindowActive)

        window.activateWindow()
        return window

    def _start_wizard_to_select_or_create_wallet(
            self, path) -> Optional[Abstract_Wallet]:
        wizard = InstallWizard(self.config,
                               self.app,
                               self.plugins,
                               gui_object=self)
        try:
            path, storage = wizard.select_storage(path, self.daemon.get_wallet)
            # storage is None if file does not exist
            if storage is None:
                wizard.path = path  # needed by trustedcoin plugin
                wizard.run('new')
                storage, db = wizard.create_storage(path)
                if db.check_unfinished_multisig():
                    wizard.show_message(_('Saved unfinished multisig wallet'))
                    return
            else:
                db = WalletDB(storage.read(), manual_upgrades=False)
                if db.upgrade_done:
                    storage.backup_old_version()
                wizard.run_upgrades(storage, db)
            if getattr(storage, 'backup_message', None):
                custom_message_box(icon=QMessageBox.Warning,
                                   parent=None,
                                   title=_('Information'),
                                   text=storage.backup_message)
                storage.backup_message = ''
            if db.check_unfinished_multisig():
                wizard.continue_multisig_setup(storage)
                storage, db = wizard.create_storage(storage.path)
                if db.check_unfinished_multisig():
                    wizard.show_message(_('Saved unfinished multisig wallet'))
                    return
        except (UserCancelled, GoBack):
            return
        except WalletAlreadyOpenInMemory as e:
            return e.wallet
        finally:
            wizard.terminate()
        # return if wallet creation is not complete
        if storage is None or db.get_action():
            return
        wallet = Wallet(db, storage, config=self.config)
        wallet.start_network(self.daemon.network)
        self.daemon.add_wallet(wallet)
        return wallet

    def close_window(self, window: ElectrumWindow):
        if window in self.windows:
            self.windows.remove(window)
        self.build_tray_menu()
        # save wallet path of last open window
        if not self.windows:
            self.config.save_last_wallet(window.wallet)
        run_hook('on_close_window', window)
        self.daemon.stop_wallet(window.wallet.storage.path)

    def init_network(self):
        # Show network dialog if config does not exist
        if self.daemon.network:
            if self.config.get('auto_connect') is None:
                wizard = InstallWizard(self.config,
                                       self.app,
                                       self.plugins,
                                       gui_object=self)
                wizard.init_network(self.daemon.network)
                wizard.terminate()

    def main(self):
        # setup Ctrl-C handling and tear-down code first, so that user can easily exit whenever
        self.app.setQuitOnLastWindowClosed(
            False)  # so _we_ can decide whether to quit
        self.app.lastWindowClosed.connect(self._maybe_quit_if_no_windows_open)
        self.app.aboutToQuit.connect(self._cleanup_before_exit)
        signal.signal(signal.SIGINT, lambda *args: self.app.quit())
        # hook for crash reporter
        Exception_Hook.maybe_setup(config=self.config)
        # first-start network-setup
        try:
            self.init_network()
        except UserCancelled:
            return
        except GoBack:
            return
        except Exception as e:
            self.logger.exception('')
            return
        # start wizard to select/create wallet
        self.timer.start()
        path = self.config.get_wallet_path(use_gui_last_wallet=True)
        try:
            if not self.start_new_window(
                    path, self.config.get('url'), app_is_starting=True):
                return
        except Exception as e:
            self.logger.error(
                "error loading wallet (or creating window for it)")
            send_exception_to_crash_reporter(e)
            # Let Qt event loop start properly so that crash reporter window can appear.
            # We will shutdown when the user closes that window, via lastWindowClosed signal.
        # main loop
        self.logger.info("starting Qt main loop")
        self.app.exec_()
        # on some platforms the exec_ call may not return, so use _cleanup_before_exit

    def stop(self):
        self.logger.info('closing GUI')
        self.app.quit()
Beispiel #41
0
class bridgePanel(QMainWindow, QObject):
    start = pyqtSignal()
    stop = pyqtSignal()

    def __init__(self, app):
        super().__init__()
        self.__v2rayshellConfigFile = {
            "preferences": {
                "v2ray-core": "",
                "v2ray-coreFilePath": "",
                "connection": {
                    "connect": "switch",
                    "interval": 45,
                    "timeout": 3,
                    "enable": True,
                    "trytimes": 3
                }
            },
            "configFiles": [{
                "enable": True,
                "hostName": "",
                "configFileName": ""
            }]
        }
        self.bridgetreasureChest = bridgetreasureChest.bridgetreasureChest()
        self.app = app
        self.translate = QCoreApplication.translate
        self.__v2rayshellVersion = "20180204"
        self.__windowTitile = "V2Ray-shell" + " " + self.__v2rayshellVersion
        self.runv2raycore = False
        self.iconStart = QIcon()
        self.iconStop = QIcon()
        self.__iconSize = QSize(32, 32)
        self.iconStart.addPixmap(QPixmap(filePath + "/icons/start.png"),
                                 QIcon.Normal, QIcon.On)
        self.iconStop.addPixmap(QPixmap(filePath + "/icons/stop.png"),
                                QIcon.Disabled, QIcon.On)
        self.currentRowRightClicked = False
        self.v2rayshellTrayIcon = QSystemTrayIcon()
        self.v2rayshellTrayIcon.setIcon(self.iconStart)
        self.v2rayshellTrayIcon.show()

        self.radioButtonGroup = QButtonGroup()

        self.setV2RayshellLanguage()
        self.trytimes = self.bridgetreasureChest.getConnectiontrytimes()
        self.interval = self.bridgetreasureChest.getConnectioninterval()
        self.proxyTryConnect = proxyTryconnect()
        if v2rayshellDebug: self.proxyTryConnect.setresetTime(6, 3)
        else: self.proxyTryConnect.setresetTime(self.interval, self.trytimes)
        self.labelBridge = (self.translate("bridgePanel", "Start/Stop"),
                            self.translate("bridgePanel", "Host Name"),
                            self.translate("bridgePanel", "Config Name"),
                            self.translate("bridgePanel", "Proxy"),
                            self.translate("bridgePanel", "Time Lag"))

        self.createBridgePanel()

    def createBridgePanel(self):
        self.setWindowTitle(self.__windowTitile)
        self.setWindowIcon(self.iconStart)
        menubar = self.menuBar()
        self.statusBar()

        self.actionNewV2rayConfigFile = QAction(
            self.translate("bridgePanel", "Add V2Ray-core Config File"), self)
        self.actionNewV2rayConfigFile.setShortcut(QKeySequence.New)
        self.actionNewV2rayConfigFile.setStatusTip(
            self.translate("bridgePanel", "Add V2Ray-core Config File"))

        self.actionSaveV2rayshellConfigFile = QAction(
            self.translate("bridgePanel", "Save V2Ray-shell Config File"),
            self)
        self.actionSaveV2rayshellConfigFile.setShortcut(QKeySequence.Save)
        self.actionSaveV2rayshellConfigFile.setStatusTip(
            self.translate("bridgePanel", "Save V2Ray-shell Config File"))

        self.actionReloadV2rayshellConfigFile = QAction(
            self.translate("bridgePanel", "Open V2Ray-shell Config File"),
            self)
        self.actionReloadV2rayshellConfigFile.setShortcut(QKeySequence.Open)
        self.actionReloadV2rayshellConfigFile.setStatusTip(
            self.translate("bridgePanel", "Open V2Ray-shell Config File"))

        self.actionQuitV2rayshellPanel = QAction(
            self.translate("bridgePanel", "Quit"), self)
        if sys.platform.startswith('win'):
            self.actionQuitV2rayshellPanel.setShortcut("Ctrl+Q")
        else:
            self.actionQuitV2rayshellPanel.setShortcut(QKeySequence.Quit)
        self.actionQuitV2rayshellPanel.setStatusTip(
            self.translate("bridgePanel", "Quit V2Ray-shell"))

        fileMenu = menubar.addMenu(self.translate("bridgePanel", "&File"))
        fileMenu.addAction(self.actionNewV2rayConfigFile)
        fileMenu.addSeparator()
        fileMenu.addAction(self.actionReloadV2rayshellConfigFile)
        fileMenu.addAction(self.actionSaveV2rayshellConfigFile)
        fileMenu.addSeparator()
        fileMenu.addAction(self.actionQuitV2rayshellPanel)

        self.texteditBridge = QTextEdit(self)
        self.texteditBridge.setReadOnly(True)

        self.tableWidgetBridge = QTableWidget()
        self.tableWidgetBridge.setRowCount(0)
        self.tableWidgetBridge.setColumnCount(5)
        self.tableWidgetBridge.setHorizontalHeaderLabels(self.labelBridge)
        self.tableWidgetBridge.setSelectionMode(
            QAbstractItemView.SingleSelection)
        self.tableWidgetBridge.setSelectionBehavior(
            QAbstractItemView.SelectRows)
        self.tableWidgetBridge.setEditTriggers(
            QAbstractItemView.NoEditTriggers)
        #self.tableWidgetBridge.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.tableWidgetBridge.setContextMenuPolicy(Qt.CustomContextMenu)

        self.popMenu = popMenu = QMenu(self.tableWidgetBridge)
        self.actionpopMenuAddV2rayConfigFile = QAction(
            self.translate("bridgePanel", "Add V2Ray Config File"), self)
        self.actionpopMenuAddV2rayConfigFile.setShortcut("Ctrl+n")
        self.actionpopMenuEditV2rayConfigFile = QAction(
            self.translate("bridgePanel", "Edit V2Ray Config File"), self)
        self.actionpopMenuProxyCheckTimeLag = QAction(
            self.translate("bridgePanel", "Proxy Time Lag Check..."), self)
        self.actionpopMenuDeleteRow = QAction(
            self.translate("bridgePanel", "Delete"), self)

        popMenu.addAction(self.actionpopMenuAddV2rayConfigFile)
        popMenu.addAction(self.actionpopMenuEditV2rayConfigFile)
        popMenu.addAction(self.actionpopMenuProxyCheckTimeLag)
        popMenu.addAction(self.actionpopMenuDeleteRow)

        self.actionopenV2rayshellPreferencesPanel = QAction(
            self.translate("bridgePanel", "preferences"), self)
        self.actionopenV2rayshellPreferencesPanel.setStatusTip(
            self.translate("bridgePanel", "Setting V2Ray-shell"))

        optionMenu = menubar.addMenu(self.translate("bridgePanel", "&options"))
        optionMenu.addAction(self.actionpopMenuProxyCheckTimeLag)
        optionMenu.addAction(self.actionopenV2rayshellPreferencesPanel)

        helpMenu = menubar.addMenu(self.translate("bridgePanel", "&help"))
        self.actioncheckv2raycoreupdate = QAction(
            self.translate("bridgePanel", "check V2Ray-core update"), self)
        self.actionv2rayshellBugreport = QAction(
            self.translate("bridgePanel", "Bug Report"), self)
        self.actionaboutv2rayshell = QAction(
            self.translate("bridgePanel", "About"), self)

        helpMenu.addAction(self.actioncheckv2raycoreupdate)
        helpMenu.addAction(self.actionv2rayshellBugreport)
        helpMenu.addAction(self.actionaboutv2rayshell)

        toolBar = QToolBar()
        self.actionV2rayStart = QAction(self.translate("bridgePanel", "Start"))
        self.actionV2rayStart.setIcon(self.style().standardIcon(
            getattr(QStyle, "SP_MediaPlay")))
        self.actionV2rayStop = QAction(self.translate("bridgePanel", "Stop"))
        self.actionV2rayStop.setIcon(self.style().standardIcon(
            getattr(QStyle, "SP_MediaStop")))
        toolBar.addAction(self.actionV2rayStart)
        toolBar.addAction(self.actionV2rayStop)
        self.addToolBar(toolBar)

        self.trayIconMenu = QMenu()
        self.v2rayshellTrayIcon.setContextMenu(self.trayIconMenu)

        self.trayIconMenushowhidePanel = QAction(
            self.translate("bridgePanel", "Show/Hide"))
        self.trayIconMenuclosePanel = QAction(
            self.translate("bridgePanel", "Quit"))

        self.trayIconMenu.addAction(self.trayIconMenushowhidePanel)
        self.trayIconMenu.addSeparator()
        self.trayIconMenu.addAction(self.trayIconMenuclosePanel)

        self.splitterBridge = QSplitter(Qt.Vertical)
        self.splitterBridge.addWidget(self.tableWidgetBridge)
        self.splitterBridge.addWidget(self.texteditBridge)

        self.setCentralWidget(self.splitterBridge)

        self.createBridgePanelSignals()

        self.onloadV2rayshellConfigFile(init=True)

        self.onv2raycoreStart()

        self.autocheckv2raycoreUpdate()

    def createBridgePanelSignals(self):
        self.actionNewV2rayConfigFile.triggered.connect(
            self.tableWidgetBridgeAddNewV2rayConfigFile)
        self.actionReloadV2rayshellConfigFile.triggered.connect(
            self.onloadV2rayshellConfigFile)
        self.actionSaveV2rayshellConfigFile.triggered.connect(
            self.onsaveV2rayshellConfigFile)
        self.actionopenV2rayshellPreferencesPanel.triggered.connect(
            self.createBridgepreferencesPanel)
        self.actionpopMenuAddV2rayConfigFile.triggered.connect(
            self.tableWidgetBridgeAddNewV2rayConfigFile)
        self.actionpopMenuEditV2rayConfigFile.triggered.connect(
            self.oncreatenauticalChartPanel)
        self.actionpopMenuDeleteRow.triggered.connect(
            self.tableWidgetBridgeDelete)
        self.actionpopMenuProxyCheckTimeLag.triggered.connect(
            self.onproxyserverTimeLagTest)
        self.actioncheckv2raycoreupdate.triggered.connect(
            self.onopenv2rayupdatePanel)
        self.actionv2rayshellBugreport.triggered.connect(self.bugReportPanel)
        self.actionQuitV2rayshellPanel.triggered.connect(self.close)
        self.actionV2rayStart.triggered.connect(self.onv2raycoreStart)
        self.actionV2rayStop.triggered.connect(self.onv2raycoreStop)
        self.actionaboutv2rayshell.triggered.connect(self.about)
        self.radioButtonGroup.buttonClicked.connect(self.onradioButtonClicked)
        self.tableWidgetBridge.cellDoubleClicked.connect(
            self.ontableWidgetBridgecellDoubleClicked)
        self.tableWidgetBridge.customContextMenuRequested.connect(
            self.ontableWidgetBridgeRightClicked)
        self.v2rayshellTrayIcon.activated.connect(self.restorebridgePanel)
        self.trayIconMenushowhidePanel.triggered.connect(
            self.onsystemTrayIconMenushowhidebridgePanel)
        self.trayIconMenuclosePanel.triggered.connect(self.close)
        self.proxyTryConnect.reconnectproxy.connect(self.swapNextConfigFile)
        self.start.connect(self.onupdateinstallFinishedstartNewV2raycore)
        self.stop.connect(self.onv2raycoreStop)

    def setV2RayshellLanguage(self):
        self.trans = QTranslator()
        language = self.bridgetreasureChest.getLanguage()
        allLanguages = self.bridgetreasureChest.getAllLanguage()
        if language and allLanguages:
            if language in allLanguages:
                self.trans.load(allLanguages[language])
                self.app.installTranslator(self.trans)

    def autocheckv2raycoreUpdate(self):
        self.v2rayshellautoUpdate = updatePanel.updateV2ray()
        self.bridgeSingal = (self.start, self.stop)
        self.trycheckUpdate = QTimer()
        self.trycheckUpdate.timeout.connect(
            lambda: self.v2rayshellautoUpdate.enableUpdateSchedule(
                self.bridgetreasureChest, self.bridgeSingal))

        self.trycheckUpdate.start(1000 * 60 * 60 *
                                  4)  ### Check every four hours
        self.trycheckUpdate.singleShot(  ### Check when the script is started
            1000 * 15,  ### fifty seconds
            lambda: self.v2rayshellautoUpdate.enableUpdateSchedule(
                self.bridgetreasureChest, self.bridgeSingal))

    def event(self, event):
        if (event.type() == QEvent.WindowStateChange and self.isMinimized()):
            self.setWindowFlags(self.windowFlags() & ~Qt.Tool)
            self.v2rayshellTrayIcon.show()
            return True
        else:
            return super(bridgePanel, self).event(event)

    def onsystemTrayIconMenushowhidebridgePanel(self):
        if self.isHidden():
            self.showNormal()
        elif self.isVisible():
            self.hide()

    def restorebridgePanel(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            if self.isVisible():
                self.hide()
            elif self.isHidden():
                self.showNormal()

    def close(self):
        super(bridgePanel, self).close()
        self.v2rayshellTrayIcon.hide()
        self.onv2raycoreStop()

    def closeEvent(self, event):
        self.close()
        event.accept()
        sys.exit(self.app.exec_())

    def onv2raycoreStop(self):
        if (self.runv2raycore):
            self.runv2raycore.stop.emit()
        try:
            ### force stop checking proxy time lag
            del self.autoCheckTimer
        except Exception:
            pass

    def onupdateinstallFinishedstartNewV2raycore(self):
        self.onloadV2rayshellConfigFile(init=True)
        self.onv2raycoreStart()

    def onv2raycoreStart(self):
        currentActiveRow = False
        rowCount = self.tableWidgetBridge.rowCount()
        for i in range(rowCount):
            currentActiveRow = self.tableWidgetBridge.cellWidget(i, 0)
            if currentActiveRow.isChecked():
                self.texteditBridge.clear()
                option = self.tableWidgetBridge.item(i, 2)
                if option:
                    option = '-config="{}" -format=json'.format(option.text())
                else:
                    option = ""
                filePath = self.bridgetreasureChest.getV2raycoreFilePath()
                if (filePath == False or filePath == ""):
                    filePath = "v2ray"
                self.runv2raycore = runV2raycore.runV2raycore(
                    outputTextEdit=self.texteditBridge,
                    v2rayPath=filePath,
                    v2rayOption=option,
                    bridgetreasureChest=self.bridgetreasureChest)

                self.runv2raycore.start.emit()
                self.autocheckProxy(i)
                break
            else:
                del currentActiveRow

    def autocheckProxy(self, row):
        ### TODO
        """
        Frequent access to the server may cause suspicion of DDOS attacks, 
        which may put the VPS server at risk.
        """
        enableAutoCheck = self.bridgetreasureChest.getConnectionEnable()

        if (enableAutoCheck):
            self.proxyStatus = proxyTest.proxyStatus()
            self.autoCheckTimer = QTimer()
            invervalTime = self.bridgetreasureChest.getConnectioninterval()
            timeout = self.bridgetreasureChest.getConnectiontimeout()
            proxyAddress = self.getProxyAddressFromTableWidget(row)

            if proxyAddress:
                self.autoCheckTimer.timeout.connect(
                    lambda: self.startCheckProxy(timeout=timeout,
                                                 proxyAddress=proxyAddress,
                                                 row=row,
                                                 proxyStatus=self.proxyStatus))

                self.bridgetreasureChest.setProxy(proxyAddress)

                if v2rayshellDebug: self.autoCheckTimer.start(6000)
                else: self.autoCheckTimer.start(1000 * invervalTime)

                self.autoCheckTimer.singleShot(
                    100,
                    lambda: self.startCheckProxy(timeout=timeout,
                                                 proxyAddress=proxyAddress,
                                                 row=row,
                                                 proxyStatus=self.proxyStatus))

    def setTableWidgetTimelag(self, row, proxyStatus):
        newlabelTimelag = self.setlabelTimeLagColor(proxyStatus)
        oldlabelTimelag = self.tableWidgetBridge.cellWidget(row, 4)
        del oldlabelTimelag
        self.tableWidgetBridge.setCellWidget(row, 4, newlabelTimelag)
        self.tableWidgetBridge.resizeColumnsToContents()

    def startCheckProxy(self, timeout, proxyAddress, row, proxyStatus):
        if (proxyAddress):
            proxyStatus.clear()
            proxyStatus.signal.connect(
                lambda: self.setTableWidgetTimelag(row, proxyStatus))

            self.proxy = proxyTest.proxyTest(proxyprotocol=proxyAddress[0],
                                             proxyhostname=proxyAddress[1],
                                             proxyhostport=int(
                                                 proxyAddress[2]),
                                             getproxyStatus=proxyStatus,
                                             timeout=int(timeout))

    def setlabelTimeLagColor(self, proxyStatus=False):
        labelTimeLag = QLabel()
        if (proxyStatus and proxyStatus.getProxyError() == False):
            labelFont = QFont()
            labelFont.setPointSize(12)
            labelFont.setBold(True)
            labelTimeLag.setFont(labelFont)
            forestGreen = "QLabel {color: rgb(34, 139, 34)}"
            darkOrange = "QLabel {color: rgb(255, 140, 0)}"
            red = "QLabel {color: rgb(194,24,7)}"

            if (proxyStatus.getElapsedTime() < 260):
                labelTimeLag.setStyleSheet(forestGreen)
            elif (proxyStatus.getElapsedTime() > 420):
                labelTimeLag.setStyleSheet(red)
            else:
                labelTimeLag.setStyleSheet(darkOrange)
            labelTimeLag.setText("{} ms".format(
                str(proxyStatus.getElapsedTime())))

            return labelTimeLag

        elif (proxyStatus and proxyStatus.getProxyError()):
            labelTimeLag.setText("{}:{}".format(
                proxyStatus.getProxyErrorString(),
                proxyStatus.getProxyErrorCode()))

            self.proxyTryConnect.trytimesDecrease()
            return labelTimeLag

    def swapNextConfigFile(self):
        self.onv2raycoreStop()
        try:
            self.trytimes = self.bridgetreasureChest.getConnectiontrytimes()
            self.interval = self.bridgetreasureChest.getConnectioninterval()
            self.proxyTryConnect.stopperiodicCheckProxyStatus()
            if v2rayshellDebug: self.proxyTryConnect.setresetTime(6, 3)
            else:
                self.proxyTryConnect.setresetTime(self.interval, self.trytimes)
        except Exception:
            self.proxyTryConnect.setresetTime(60, 3)

        if (self.bridgetreasureChest.connectionisSwitch()):
            ### swap next row's configFile
            buttons = self.radioButtonGroup.buttons()
            buttonsNumber = len(buttons)
            activeRow = False
            for i in range(buttonsNumber):
                if buttons[i].isChecked():
                    buttons[i].setChecked(False)
                    if i == buttonsNumber - 1:
                        buttons[0].setChecked(True)
                        activeRow = 0
                        break
                    else:
                        buttons[i + 1].setChecked(True)
                        activeRow = i + 1
                    break

            ### change the row icons
            for i in range(buttonsNumber):
                widget = self.tableWidgetBridge.cellWidget(i, 0)
                if (widget):
                    widget.setIcon(self.iconStop)
                    widget.setIconSize(self.__iconSize)
                    if (widget.isChecked()):
                        pass
            widget = self.tableWidgetBridge.cellWidget(activeRow, 0)
            if widget:
                widget.setIcon(self.iconStart)
                widget.setIconSize(self.__iconSize)

        self.onv2raycoreStart()

    def onopenv2rayupdatePanel(self):
        currentActiveRow = False
        rowCount = self.tableWidgetBridge.rowCount()
        currentRow = False
        for i in range(rowCount):
            currentActiveRow = self.tableWidgetBridge.cellWidget(i, 0)
            if currentActiveRow.isChecked():
                currentRow = i
                break

        if (currentActiveRow and currentActiveRow.isChecked()):
            proxy = self.tableWidgetBridge.item(currentRow, 3)
            proxy = proxy.text().split(":")
            protocol = QNetworkProxy.Socks5Proxy
            if (proxy[0] == "socks"):
                protocol = QNetworkProxy.Socks5Proxy
            elif (proxy[0] == "http"):
                protocol = QNetworkProxy.HttpProxy
            hostName = proxy[1]
            hostPort = int(proxy[2])

            v2rayAPI = updatePanel.v2rayAPI()
            self.createupdatePanel = updatePanel.v2rayUpdatePanel(
                v2rayapi=v2rayAPI,
                protocol=protocol,
                proxyhostName=hostName,
                port=hostPort,
                bridgetreasureChest=self.bridgetreasureChest)
            self.createupdatePanel.createPanel()
            self.createupdatePanel.setAttribute(Qt.WA_DeleteOnClose)
            self.createupdatePanel.setWindowIcon(self.iconStart)
            self.createupdatePanel.setWindowTitle(
                self.translate("bridgePanel", "Check V2Ray-core update"))
            self.createupdatePanel.resize(QSize(1024, 320))
            self.createupdatePanel.move(
                QApplication.desktop().screen().rect().center() -
                self.createupdatePanel.rect().center())
            self.createupdatePanel.show()
            self.createupdatePanel.exec_()
        else:
            self.noPoxyServerRunning()

    def ontableWidgetBridgeRightClicked(self, pos):
        index = self.tableWidgetBridge.indexAt(pos)
        clickedRow.rightClickedRow = index.row()
        clickedRow.mousePos = QCursor().pos()
        self.popMenu.move(QCursor().pos())
        self.popMenu.show()

    def ontableWidgetBridgecellDoubleClicked(self, row, column):
        if (column == 1):
            hostName, ok = QInputDialog.getText(
                self, self.translate("bridgePanel", 'Host Name'),
                self.translate("bridgePanel", 'Enter Host Name:'))
            if (ok):
                self.tableWidgetBridge.setItem(row, column,
                                               QTableWidgetItem(str(hostName)))
                self.tableWidgetBridge.resizeColumnsToContents()
        elif (column == 2):
            fileNames = self.onopenV2rayConfigJSONFile()
            if (fileNames):
                for fileName in fileNames:
                    self.tableWidgetBridge.setItem(
                        row, column, QTableWidgetItem(str(fileName)))
                    self.tableWidgetBridge.resizeColumnsToContents()
        elif (column == 3):
            self.onproxyserverTimeLagTest()
        elif (column == 4):
            self.onproxyserverTimeLagTest()

    def getProxyAddressFromTableWidget(self, row):
        proxy = self.tableWidgetBridge.item(row, 3)
        try:
            proxy = proxy.text().split(":")
        except Exception:
            return False

        if (proxy[0] == "socks"):
            proxy[0] = QNetworkProxy.Socks5Proxy
        elif (proxy[0] == "http"):
            proxy[0] = QNetworkProxy.HttpProxy

        if len(proxy) < 3: return False
        else: return proxy

    def onproxyserverTimeLagTest(self):
        proxyStatus = proxyTest.proxyStatus()
        """
        right clicked mouse button pop a menu check proxy
        """
        currentActiveRow = False
        rowCount = self.tableWidgetBridge.rowCount()
        currentRow = False
        for i in range(rowCount):
            currentActiveRow = self.tableWidgetBridge.cellWidget(i, 0)
            if currentActiveRow.isChecked():
                currentRow = i
                break
        if (currentActiveRow and currentActiveRow.isChecked()):
            proxy = self.getProxyAddressFromTableWidget(currentRow)
            protocol = proxy[0]
            hostName = proxy[1]
            hostPort = int(proxy[2])

            proxy = proxyTest.proxyTestPanel(proxyhostname=hostName,
                                             proxyhostport=hostPort,
                                             proxyprotocol=protocol,
                                             getproxyStatus=proxyStatus)
            proxy.createproxyTestPanel()
            proxy.setAttribute(Qt.WA_DeleteOnClose)
            proxy.setWindowTitle(
                self.translate("bridgePanel", "Proxy Time Lag Check"))
            proxy.setWindowIcon(self.iconStart)
            proxy.resize(QSize(600, 480))
            proxy.move(QApplication.desktop().screen().rect().center() -
                       proxy.rect().center())
            proxy.show()
            proxy.exec_()
        else:
            self.noPoxyServerRunning()

    def noPoxyServerRunning(self):
        warningPanel = QDialog()
        warningPanel.setAttribute(Qt.WA_DeleteOnClose)
        warningPanel.setWindowTitle(self.translate("bridgePanel",
                                                   "Warnnig..."))
        warningPanel.setWindowIcon(self.iconStop)
        labelMsg = QLabel(
            self.translate(
                "bridgePanel",
                "There no any server is running, \n[File]->[Add V2Ray-core Config File] (Ctrl+n) add a config.json."
            ))
        vbox = QVBoxLayout()
        vbox.addWidget(labelMsg)
        warningPanel.setLayout(vbox)
        warningPanel.move(QApplication.desktop().screen().rect().center() -
                          warningPanel.rect().center())
        warningPanel.open()
        warningPanel.exec_()

    def getProxyAddressFromJSONFile(self, filePath):
        from bridgehouse.editMap.port import treasureChest, openV2rayJSONFile
        tempTreasureChest = treasureChest.treasureChest()
        openV2rayJSONFile.openV2rayJSONFile(
            filePath, tempTreasureChest, disableLog=True).initboundJSONData()
        inbound = tempTreasureChest.getInbound()
        if (inbound):
            protocol = inbound["protocol"]
            ipAddress = inbound["listen"]
            port = inbound["port"]
            if (protocol == "socks" or protocol == "http"):
                return "{}:{}:{}".format(protocol, ipAddress, port)
            else:
                return False
        else:
            return False

    def onradioButtonClicked(self, e):
        rowCount = self.tableWidgetBridge.rowCount()
        #radioButtonClickedRow = 0
        for i in range(rowCount):
            widget = self.tableWidgetBridge.cellWidget(i, 0)
            if (widget):
                widget.setIcon(self.iconStop)
                widget.setIconSize(self.__iconSize)
                if (widget.isChecked()):
                    #radioButtonClickedRow = i
                    pass
        e.setIcon(self.iconStart)
        e.setIconSize(self.__iconSize)

    def onloadV2rayshellConfigFile(self, init=False):
        """
        when the script first start, and auto load v2ray-shell config file.
        """
        if init:
            self.settingv2rayshelltableWidget()
        else:

            def openV2rayshellConfigFile():
                options = QFileDialog.Options()
                filePath, _ = QFileDialog.getOpenFileName(
                    self,
                    self.translate("bridgePanel",
                                   "Open V2Ray-sehll Config File"),
                    "",
                    "V2Ray-shell config file (*.v2rayshell)",
                    options=options)
                if (filePath):
                    self.bridgetreasureChest.clear()
                    self.tableWidgetBridge.setRowCount(0)
                    self.bridgetreasureChest.inibridgeJSONData(
                        v2rayshellConfigFileName=filePath)
                    self.settingv2rayshelltableWidget()

            openV2rayshellConfigFile()

    def onopenV2rayConfigJSONFile(self):
        """
        open a new v2ray config file to tabelWidget
        """
        options = QFileDialog.Options()
        filePaths, _ = QFileDialog.getOpenFileNames(
            self,
            self.translate("bridgePanel", "Open V2Ray-core Config File"),
            "",
            """
                                                  V2Ray config file (*.json);;
                                                  All File (*);;
                                                  """,
            options=options)

        if (filePaths):
            return filePaths
        else:
            return False

    def createBridgepreferencesPanel(self):
        self.createpreferencesPanel = bridgePreference.bridgepreferencesPanel(
            self.bridgetreasureChest)
        self.createpreferencesPanel.setAttribute(Qt.WA_DeleteOnClose)
        self.createpreferencesPanel.createpreferencesPanel()
        self.createpreferencesPanel.setWindowIcon(self.iconStart)
        self.createpreferencesPanel.move(
            QApplication.desktop().screen().rect().center() -
            self.createpreferencesPanel.rect().center())
        self.createpreferencesPanel.open()
        self.createpreferencesPanel.exec_()

    def settingv2rayshelltableWidget(self):
        v2rayConfigFiles = self.bridgetreasureChest.getV2raycoreconfigFiles()
        if v2rayConfigFiles == False: return
        v2rayConfigFilesNumber = len(v2rayConfigFiles)
        if (v2rayConfigFilesNumber > 0):
            self.tableWidgetBridge.setRowCount(0)
            for i in range(v2rayConfigFilesNumber):
                try:
                    enable = bool(v2rayConfigFiles[i]["enable"])
                    hostName = str(v2rayConfigFiles[i]["hostName"])
                    configFileName = str(v2rayConfigFiles[i]["configFileName"])
                except Exception:
                    pass

                radioButtonStopStart = QRadioButton(self)
                radioButtonStopStart.setIcon(
                    self.iconStop if not enable else self.iconStart)
                radioButtonStopStart.setChecked(True if enable else False)
                radioButtonStopStart.setIconSize(self.__iconSize)
                self.radioButtonGroup.addButton(radioButtonStopStart)
                self.tableWidgetBridge.setRowCount(i + 1)
                self.tableWidgetBridge.setCellWidget(i, 0,
                                                     radioButtonStopStart)
                self.tableWidgetBridge.setItem(i, 1,
                                               QTableWidgetItem(hostName))
                self.tableWidgetBridge.setItem(
                    i, 2, QTableWidgetItem(configFileName))
                self.tableWidgetBridge.setItem(
                    i, 3,
                    QTableWidgetItem(
                        self.getProxyAddressFromJSONFile(configFileName)))
                self.tableWidgetBridge.resizeColumnsToContents()
                #self.tableWidgetBridge.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

    def onsaveV2rayshellConfigFile(self):
        self.bridgetreasureChest.clearconfigFiles()
        rowCount = self.tableWidgetBridge.rowCount()
        for i in range(rowCount):
            enable = self.tableWidgetBridge.cellWidget(i, 0)
            if enable and enable.isChecked():
                enable = True
            else:
                enable = False

            hostName = self.tableWidgetBridge.item(i, 1)
            if hostName:
                hostName = hostName.text()
            else:
                hostName = ""

            config = self.tableWidgetBridge.item(i, 2)
            if config:
                config = config.text()
            else:
                config = ""
            self.bridgetreasureChest.setV2raycoreconfigFiles(
                enable, hostName, configFileName=config)
        self.bridgetreasureChest.save.emit()

    def oncreatenauticalChartPanel(self):
        v2rayConfigFileName = self.tableWidgetBridge.item(
            clickedRow.rightClickedRow, 2)
        if (v2rayConfigFileName):
            nc = nauticalChartPanel.nauticalChartPanel(
                v2rayConfigFileName.text())
            nc.setAttribute(Qt.WA_DeleteOnClose)
            nc.createPanel()
            nc.setWindowTitle(
                self.translate("bridgePanel", "V2Ray config file edit"))
            nc.setWindowIcon(self.iconStart)
            nc.setGeometry(0, 0, 1024, 768)
            ### move widget to center
            nc.move(QApplication.desktop().screen().rect().center() -
                    nc.rect().center())
            nc.show()
            nc.exec_()

    def tableWidgetBridgeAddNewV2rayConfigFile(self):
        configFileNames = self.onopenV2rayConfigJSONFile()
        if (configFileNames):
            for configFileName in configFileNames:
                rowCount = self.tableWidgetBridge.rowCount()
                radioButtonStopStart = QRadioButton(self)
                radioButtonStopStart.setIcon(self.iconStop)
                radioButtonStopStart.setIconSize(self.__iconSize)
                self.radioButtonGroup.addButton(radioButtonStopStart)

                self.tableWidgetBridge.setRowCount(rowCount + 1)
                self.tableWidgetBridge.setCellWidget(rowCount, 0,
                                                     radioButtonStopStart)
                self.tableWidgetBridge.setItem(rowCount, 1,
                                               QTableWidgetItem(""))
                self.tableWidgetBridge.setItem(
                    rowCount, 2, QTableWidgetItem(configFileName))
                self.tableWidgetBridge.setItem(
                    rowCount, 3,
                    QTableWidgetItem(
                        self.getProxyAddressFromJSONFile(configFileName)))
                self.tableWidgetBridge.resizeColumnsToContents()
        else:
            pass

    def tableWidgetBridgeDelete(self):
        self.tableWidgetBridge.removeRow(clickedRow.rightClickedRow)

    def validateV2rayJSONFile(self, JSONData):
        """
        simply validate a V2Ray json file.
        """
        try:
            JSONData["inbound"]
            JSONData["outbound"]
        except KeyError:
            return False
        else:
            return True

    def about(self):
        NineteenEightySeven = QLabel(
            self.translate(
                "bridgePanel",
                """Across the Great Wall, we can reach every corner in the world."""
            ))  ### Crossing the Great Wall to Join the World
        Timeless = QLabel(
            self.translate(
                "bridgePanel",
                """You weren't thinking about that when you were creating it.\nBecause if you did? You never would have gone through with it."""
            ))
        DwayneRichardHipp = QLabel(
            self.translate(
                "bridgePanel",
                """May you do good and not evil.\nMay you find forgiveness for yourself and forgive others.\nMay you share freely, never taking more than you give."""
            ))
        vbox = QVBoxLayout()
        vbox.addWidget(NineteenEightySeven)
        vbox.addWidget(Timeless)
        vbox.addWidget(DwayneRichardHipp)

        dialogAbout = QDialog()
        dialogAbout.setAttribute(Qt.WA_DeleteOnClose)
        dialogAbout.setWindowTitle(
            self.translate("bridgePanel", "About V2Ray-shell"))
        dialogAbout.setWindowIcon(self.iconStart)
        dialogAbout.move(QApplication.desktop().screen().rect().center() -
                         dialogAbout.rect().center())
        dialogAbout.setLayout(vbox)
        dialogAbout.open()
        dialogAbout.exec_()

    def bugReportPanel(self):
        self.bugReport = bugReport.bugReport()
        self.bugReport.setAttribute(Qt.WA_DeleteOnClose)
        self.bugReport.setWindowTitle(
            self.translate("bridgePanel", "Bug Report"))
        self.bugReport.setWindowIcon(self.iconStart)
        self.bugReport.createPanel()
        self.bugReport.show()
        self.bugReport.setGeometry(250, 150, 1024, 768)
Beispiel #42
0
class BlenderLauncher(QMainWindow, BaseWindow, Ui_MainWindow):
    def __init__(self, app):
        super().__init__()
        self.setupUi(self)

        # Server
        self.server = QLocalServer()
        self.server.listen("blender-launcher-server")
        self.server.newConnection.connect(self.new_connection)

        # Global scope
        self.app = app
        self.favorite = None
        self.status = "None"
        self.app_state = AppState.IDLE
        self.cashed_builds = []
        self.manager = PoolManager(200)
        self.timer = None

        # Setup window
        self.setWindowTitle("Blender Launcher")
        self.app.setWindowIcon(QIcon(":resources/icons/tray.ico"))

        # Setup font
        QFontDatabase.addApplicationFont(
            ":/resources/fonts/OpenSans-SemiBold.ttf")
        self.font = QFont("Open Sans SemiBold", 10)
        self.font.setHintingPreference(QFont.PreferNoHinting)
        self.app.setFont(self.font)

        # Setup style
        file = QFile(":/resources/styles/global.qss")
        file.open(QFile.ReadOnly | QFile.Text)
        self.style_sheet = QTextStream(file).readAll()
        self.app.setStyleSheet(self.style_sheet)

        # Check library folder
        if is_library_folder_valid() is False:
            self.dlg = DialogWindow(
                self, title="Information",
                text="First, choose where Blender\nbuilds will be stored",
                accept_text="Continue", cancel_text=None, icon=DialogIcon.INFO)
            self.dlg.accepted.connect(self.set_library_folder)
        else:
            self.draw()

    def set_library_folder(self):
        library_folder = Path.cwd().as_posix()
        new_library_folder = QFileDialog.getExistingDirectory(
            self, "Select Library Folder", library_folder)

        if new_library_folder:
            set_library_folder(new_library_folder)
            self.draw()

    def draw(self):
        self.HeaderLayout = QHBoxLayout()
        self.HeaderLayout.setContentsMargins(1, 1, 1, 0)
        self.HeaderLayout.setSpacing(0)
        self.CentralLayout.addLayout(self.HeaderLayout)

        self.SettingsButton = \
            QPushButton(QIcon(":resources/icons/settings.svg"), "")
        self.SettingsButton.setIconSize(QSize(20, 20))
        self.SettingsButton.setFixedSize(36, 32)
        self.WikiButton = \
            QPushButton(QIcon(":resources/icons/wiki.svg"), "")
        self.WikiButton.setIconSize(QSize(20, 20))
        self.WikiButton.setFixedSize(36, 32)
        self.MinimizeButton = \
            QPushButton(QIcon(":resources/icons/minimize.svg"), "")
        self.MinimizeButton.setIconSize(QSize(20, 20))
        self.MinimizeButton.setFixedSize(36, 32)
        self.CloseButton = \
            QPushButton(QIcon(":resources/icons/close.svg"), "")
        self.CloseButton.setIconSize(QSize(20, 20))
        self.CloseButton.setFixedSize(36, 32)
        self.HeaderLabel = QLabel("Blender Launcher")
        self.HeaderLabel.setAlignment(Qt.AlignCenter)

        self.HeaderLayout.addWidget(self.SettingsButton, 0, Qt.AlignLeft)
        self.HeaderLayout.addWidget(self.WikiButton, 0, Qt.AlignLeft)
        self.HeaderLayout.addWidget(self.HeaderLabel, 1)
        self.HeaderLayout.addWidget(self.MinimizeButton, 0, Qt.AlignRight)
        self.HeaderLayout.addWidget(self.CloseButton, 0, Qt.AlignRight)

        self.SettingsButton.setProperty("HeaderButton", True)
        self.WikiButton.setProperty("HeaderButton", True)
        self.MinimizeButton.setProperty("HeaderButton", True)
        self.CloseButton.setProperty("HeaderButton", True)
        self.CloseButton.setProperty("CloseButton", True)

        # Tab layout
        self.TabWidget = QTabWidget()
        self.CentralLayout.addWidget(self.TabWidget)

        self.LibraryTab = QWidget()
        self.LibraryTabLayout = QVBoxLayout()
        self.LibraryTabLayout.setContentsMargins(0, 0, 0, 0)
        self.LibraryTab.setLayout(self.LibraryTabLayout)
        self.TabWidget.addTab(self.LibraryTab, "Library")

        self.DownloadsTab = QWidget()
        self.DownloadsTabLayout = QVBoxLayout()
        self.DownloadsTabLayout.setContentsMargins(0, 0, 0, 0)
        self.DownloadsTab.setLayout(self.DownloadsTabLayout)
        self.TabWidget.addTab(self.DownloadsTab, "Downloads")

        self.LibraryToolBox = BaseToolBoxWidget(self)

        self.LibraryStableListWidget = \
            self.LibraryToolBox.add_list_widget("Stable Releases")
        self.LibraryDailyListWidget = \
            self.LibraryToolBox.add_list_widget("Daily Builds")
        self.LibraryExperimentalListWidget = \
            self.LibraryToolBox.add_list_widget("Experimental Branches")
        self.LibraryCustomListWidget = \
            self.LibraryToolBox.add_list_widget("Custom Builds")
        self.LibraryTab.layout().addWidget(self.LibraryToolBox)

        self.DownloadsToolBox = BaseToolBoxWidget(self)

        self.DownloadsStableListWidget = \
            self.DownloadsToolBox.add_list_widget("Stable Releases")
        self.DownloadsDailyListWidget = \
            self.DownloadsToolBox.add_list_widget("Daily Builds")
        self.DownloadsExperimentalListWidget = \
            self.DownloadsToolBox.add_list_widget("Experimental Branches")
        self.DownloadsTab.layout().addWidget(self.DownloadsToolBox)

        self.LibraryToolBox.setCurrentIndex(get_default_library_page())

        # Connect buttons
        self.SettingsButton.clicked.connect(self.show_settings_window)
        self.WikiButton.clicked.connect(lambda: webbrowser.open(
            "https://github.com/DotBow/Blender-Launcher/wiki"))
        self.MinimizeButton.clicked.connect(self.showMinimized)
        self.CloseButton.clicked.connect(self.close)

        self.StatusBar.setFont(self.font)
        self.statusbarLabel = QLabel()
        self.statusbarVersion = QLabel(self.app.applicationVersion())
        self.StatusBar.addPermanentWidget(self.statusbarLabel, 1)
        self.StatusBar.addPermanentWidget(self.statusbarVersion)

        # Draw library
        self.draw_library()

        # Setup tray icon context Menu
        quit_action = QAction("Quit", self)
        quit_action.triggered.connect(self.quit)
        hide_action = QAction("Hide", self)
        hide_action.triggered.connect(self.hide)
        show_action = QAction("Show", self)
        show_action.triggered.connect(self._show)
        launch_favorite_action = QAction(
            QIcon(":resources/icons/favorite.svg"), "Blender", self)
        launch_favorite_action.triggered.connect(self.launch_favorite)

        tray_menu = QMenu()
        tray_menu.setFont(self.font)
        tray_menu.addAction(launch_favorite_action)
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)

        # Setup tray icon
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(QIcon(":resources/icons/tray.ico"))
        self.tray_icon.setToolTip("Blender Launcher")
        self.tray_icon.activated.connect(self.tray_icon_activated)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

        # Forse style update
        self.style().unpolish(self.app)
        self.style().polish(self.app)

        # Show window
        if get_launch_minimized_to_tray() is False:
            self._show()

    def _show(self):
        self.activateWindow()
        self.show()
        self.set_status()

    def launch_favorite(self):
        try:
            self.favorite.launch()
        except Exception:
            self.dlg = DialogWindow(
                self, text="Favorite build not found!",
                accept_text="OK", cancel_text=None)

    def tray_icon_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self._show()
        elif reason == QSystemTrayIcon.MiddleClick:
            self.launch_favorite()

    def quit(self):
        download_widgets = []

        download_widgets.extend(self.DownloadsStableListWidget.items())
        download_widgets.extend(self.DownloadsDailyListWidget.items())
        download_widgets.extend(self.DownloadsExperimentalListWidget.items())

        for widget in download_widgets:
            if widget.state == DownloadState.DOWNLOADING:
                self.dlg = DialogWindow(
                    self, title="Warning", text="Download task in progress!<br>\
                    Are you sure you want to quit?",
                    accept_text="Yes", cancel_text="No",
                    icon=DialogIcon.WARNING)

                self.dlg.accepted.connect(self.quit2)
                return

        self.quit2()

    def quit2(self):
        if self.timer is not None:
            self.timer.cancel()

        self.tray_icon.hide()
        self.app.quit()

    def draw_library(self, clear=False):
        self.set_status("Reading local builds")

        if clear:
            self.timer.cancel()
            self.scraper.quit()
            self.DownloadsStableListWidget.clear()
            self.DownloadsDailyListWidget.clear()
            self.DownloadsExperimentalListWidget.clear()

        self.favorite = None

        self.LibraryStableListWidget.clear()
        self.LibraryDailyListWidget.clear()
        self.LibraryExperimentalListWidget.clear()

        self.library_drawer = LibraryDrawer(self)
        self.library_drawer.build_found.connect(self.draw_to_library)
        self.library_drawer.finished.connect(self.draw_downloads)
        self.library_drawer.start()

    def draw_downloads(self):
        self.app_state = AppState.CHECKINGBUILDS
        self.set_status("Checking for new builds")
        self.scraper = Scraper(self, self.manager)
        self.scraper.links.connect(self.draw_new_builds)
        self.scraper.error.connect(self.connection_error)
        self.scraper.start()

    def connection_error(self):
        set_locale()
        utcnow = strftime(('%H:%M'), localtime())
        self.set_status("Connection Error at " + utcnow)
        self.app_state = AppState.IDLE

        self.timer = threading.Timer(600.0, self.draw_downloads)
        self.timer.start()

    def draw_new_builds(self, builds):
        self.cashed_builds.clear()
        self.cashed_builds.extend(builds)

        library_widgets = []
        download_widgets = []

        library_widgets.extend(self.LibraryStableListWidget.items())
        library_widgets.extend(self.LibraryDailyListWidget.items())
        library_widgets.extend(self.LibraryExperimentalListWidget.items())

        download_widgets.extend(self.DownloadsStableListWidget.items())
        download_widgets.extend(self.DownloadsDailyListWidget.items())
        download_widgets.extend(self.DownloadsExperimentalListWidget.items())

        for widget in download_widgets:
            if widget.build_info in builds:
                builds.remove(widget.build_info)
            elif widget.state != DownloadState.DOWNLOADING:
                widget.destroy()

        for widget in library_widgets:
            if widget.build_info in builds:
                builds.remove(widget.build_info)

        for build_info in builds:
            self.draw_to_downloads(build_info)

        set_locale()
        utcnow = strftime(('%H:%M'), localtime())
        self.set_status("Last check at " + utcnow)
        self.app_state = AppState.IDLE

        self.timer = threading.Timer(600.0, self.draw_downloads)
        self.timer.start()

    def draw_from_cashed(self, build_info):
        if self.app_state == AppState.IDLE:
            if build_info in self.cashed_builds:
                i = self.cashed_builds.index(build_info)
                self.draw_to_downloads(self.cashed_builds[i])

    def draw_to_downloads(self, build_info):
        branch = build_info.branch

        if branch == 'stable':
            list_widget = self.DownloadsStableListWidget
        elif branch == 'daily':
            list_widget = self.DownloadsDailyListWidget
        else:
            list_widget = self.DownloadsExperimentalListWidget

        item = BaseListWidgetItem(build_info.commit_time)
        widget = DownloadWidget(self, list_widget, item, build_info)
        item.setSizeHint(widget.sizeHint())
        list_widget.addItem(item)
        list_widget.setItemWidget(item, widget)

    def draw_to_library(self, path):
        category = Path(path).parent.name

        if category == 'stable':
            list_widget = self.LibraryStableListWidget
        elif category == 'daily':
            list_widget = self.LibraryDailyListWidget
        elif category == 'experimental':
            list_widget = self.LibraryExperimentalListWidget
        elif category == 'custom':
            list_widget = self.LibraryCustomListWidget
        else:
            return

        item = BaseListWidgetItem()
        widget = LibraryWidget(self, item, path, list_widget)
        list_widget.insertItem(0, item)
        list_widget.setItemWidget(item, widget)

    def set_status(self, status=None):
        if status is not None:
            self.status = status

        self.statusbarLabel.setText("Status: {0}".format(self.status))

    def show_settings_window(self):
        self.settings_window = SettingsWindow(self)

    def clear_temp(self):
        temp_folder = Path(get_library_folder()) / ".temp"
        self.remover = Remover(temp_folder)
        self.remover.start()

    def closeEvent(self, event):
        event.ignore()
        self.hide()

    def new_connection(self):
        self._show()
Beispiel #43
0
class PlayerUi(QDialog):
    def __init__(self, parent = None):
        super(PlayerUi, self).__init__(parent)
        self.create_actions()
        self.setup_ui()
        self.managePage.ui_initial()
        self.load_style_sheet(Configures.QssFileDefault, Configures.IconsDir)
    
    def load_style_sheet(self, sheetName, iconsDir):
        """load qss file"""
        print('Using qss file: %s'%sheetName)
        qss = QFile(sheetName)
        qss.open(QFile.ReadOnly)
        styleSheet = str(qss.readAll(), encoding='utf8').replace(':PathPrefix', iconsDir)
        QApplication.instance().setStyleSheet(styleSheet)
        qss.close()
    
    def create_connections(self):
        self.searchBox.search_musics_signal.connect(self.search_musics_online)
        self.searchBox.searchtype_changed_signal.connect(self.managePage.searchFrame.searchtype_changed)
        self.aboutButton.clicked.connect(self.show_about_page)
        self.preferenceButton.clicked.connect(self.show_settings_frame)
        self.minButton.clicked.connect(self.show_minimized)
        self.closeButton.clicked.connect(self.close_button_acted)
        self.playbackPanel.show_artist_info_signal.connect(self.show_artist_info)
        self.playbackPanel.dont_hide_main_window_signal.connect(self.dont_hide_mainwindow)
        self.managePage.playlistWidget.show_artist_info_signal.connect(self.show_artist_info)
        self.managePage.playlistWidget.show_song_info_signal.connect(self.show_song_info)
        self.managePage.playlistWidget.musics_added_signal.connect(self.hide_song_info_page)
        self.managePage.playlistWidget.musics_removed_signal.connect(self.hide_song_info_page)
        self.managePage.playlistWidget.musics_cleared_signal.connect(self.hide_song_info_page)
        self.managePage.playlist_removed_signal.connect(self.hide_song_info_page)
        self.managePage.playlist_renamed_signal.connect(self.hide_song_info_page)
        self.managePage.playlistWidget.playlist_changed_signal.connect(self.hide_song_info_page)

    def setup_ui(self):        
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowIcon(QIcon(IconsHub.Xyplayer))
        self.setFocusPolicy(Qt.NoFocus)
        self.setObjectName(app_name)
        self.setWindowFlags(Qt.MSWindowsFixedSizeDialogHint | Qt.FramelessWindowHint)
        self.setMaximumSize(QSize(Configures.WindowWidth, Configures.WindowHeight))
        self.setGeometry((Configures.DesktopSize.width() - Configures.WindowWidth)//2, (Configures.DesktopSize.height() - Configures.WindowHeight)//2, Configures.WindowWidth, Configures.WindowHeight)
        self.setAttribute(Qt.WA_QuitOnClose,True)
        self.dragPosition = QPoint(0, 0)
        self.mousePressedFlag = False

#title_widgets
        self.titleIconLabel = QLabel()
        pixmap = QPixmap(IconsHub.Xyplayer)
        self.titleIconLabel.setFixedSize(18, 18)
        self.titleIconLabel.setScaledContents(True)
        self.titleIconLabel.setPixmap(pixmap)
        self.titleLabel = QLabel("XYPLAYER")
        self.titleLabel.setObjectName('titleLabel')
        self.titleLabel.setFixedHeight(30)
        self.titleLabel.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        self.titleLabel.setObjectName('titleLabel')
        self.minButton = PushButton()
        self.minButton.setToolTip(self.tr('最小化'))
        self.minButton.setFocusPolicy(Qt.NoFocus)
        self.closeButton = PushButton()
        self.closeButton.setToolTip(self.tr('关闭主面板'))
        self.closeButton.setFocusPolicy(Qt.NoFocus)
        self.aboutButton = PushButton()
        self.aboutButton.setToolTip(self.tr('关于'))
        self.aboutButton.setFocusPolicy(Qt.NoFocus)
        self.preferenceButton = PushButton()
        self.preferenceButton.setToolTip(self.tr('选项'))
        self.preferenceButton.setFocusPolicy(Qt.NoFocus)
        self.minButton.loadPixmap(IconsHub.MinButton)
        self.closeButton.loadPixmap(IconsHub.CloseButton)
        self.aboutButton.loadPixmap(IconsHub.AboutButton)
        self.preferenceButton.loadPixmap(IconsHub.PreferenceButton)
        
#管理页面
        self.managePage = manage_page.ManagePage()

#播放器
        self.playbackPanel = playback_panel.PlaybackPanel()

        self.mainPageButton = QToolButton(self, clicked = self.show_lyric_text)
        self.mainPageButton.setToolTip('返回歌词页面')
        self.mainPageButton.hide()
        self.mainPageButton.setGeometry(625, 5, 30, 30)
        self.mainPageButton.setObjectName('homeButton')
        self.mainPageButton.setFocusPolicy(Qt.NoFocus)
        self.mainPageButton.setIcon(QIcon(IconsHub.Home))
        self.mainPageButton.setIconSize(QSize(20, 20))
        self.pageLabel = QLabel(self)
        self.pageLabel.setObjectName('pageLabel')
        self.pageLabel.hide()
        self.pageLabel.setGeometry(660, 5, 100, 30)
        self.searchBox = search_page.SearchBox(self)
        self.searchBox.setGeometry(180, 6, 280, 28)

#综合布局 
        titleLayout = QHBoxLayout()
        titleLayout.setContentsMargins(3, 0, 0, 0)
        titleLayout.setSpacing(0)
        titleLayout.addWidget(self.titleIconLabel)
        titleLayout.addSpacing(2)
        titleLayout.addWidget(self.titleLabel)
        titleLayout.addStretch()
        titleLayout.addWidget(self.aboutButton)
        titleLayout.addWidget(self.preferenceButton)
        titleLayout.addWidget(self.minButton)
        titleLayout.addWidget(self.closeButton)

#主堆栈框
        self.mainStack = QStackedWidget()
        
        mainLayout = QVBoxLayout(self)
        mainLayout.setSpacing(2)
        mainLayout.setContentsMargins(3, 0, 3, 0)
        mainLayout.addLayout(titleLayout)
        mainLayout.addWidget(self.managePage)
        mainLayout.addWidget(self.playbackPanel)

#创建托盘图标
        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setIcon(QIcon(IconsHub.Xyplayer))
        self.showDesktopLyricAction = QAction(
             QIcon(IconsHub.DesktopLyric), "开启桌面歌词", 
                self,  triggered = self.playbackPanel.show_desktop_lyric )
        trayMenu = QMenu()
        trayMenu.addAction(self.showMainWindowAction)
        trayMenu.addAction(self.showDesktopLyricAction)
        trayMenu.addSeparator()
        trayMenu.addAction(self.playbackPanel.get_previous_button_action())
        trayMenu.addAction(self.playbackPanel.get_play_button_action())
        trayMenu.addAction(self.playbackPanel.get_next_button_action())
        trayMenu.addAction(self.playbackPanel.get_stop_button_action())
        trayMenu.addSeparator()
        trayMenu.addAction(self.playmodeRandomAction)
        trayMenu.addAction(self.playmodeOrderAction)
        trayMenu.addAction(self.playmodeSingleAction)
        trayMenu.addSeparator()
        trayMenu.addAction(self.exitNowAction)
        self.trayIcon.setContextMenu(trayMenu)
        self.trayIcon.show()

    def create_actions(self):
        self.showMainWindowAction = QAction(
             QIcon(IconsHub.Home), "隐藏主界面", 
                self,  triggered = self.show_mainwindow )
        
        self.playmodeOrderAction = QAction(
            QIcon(IconsHub.PlaymodeSelectedNo),  Configures.PlaymodeOrderText, 
                self, triggered = self.playmode_order_seted)
                
        self.playmodeRandomAction = QAction(
            QIcon(IconsHub.PlaymodeSelected),  Configures.PlaymodeRandomText, 
                self, triggered = self.playmode_random_seted)
        
        self.playmodeSingleAction = QAction(
            QIcon(IconsHub.PlaymodeSelectedNo),  Configures.PlaymodeSingleText, 
                self, triggered = self.playmode_single_seted)
        
        self.exitNowAction = QAction(
            QIcon(IconsHub.Close),  "立即退出", 
                self, triggered = self.close)
                
        self.playmodeActions = [self.playmodeRandomAction, self.playmodeOrderAction, self.playmodeSingleAction]
    
    def mousePressEvent(self, event):
        self.mousePressedFlag = True
        self.dragPosition = event.globalPos() - self.frameGeometry().topLeft()
        event.accept()
 
    def mouseReleaseEvent(self, event):
        self.mousePressedFlag = False
    
    def mouseMoveEvent(self, event):
        if self.mousePressedFlag:
            self.move(event.globalPos() - self.dragPosition)
            event.accept()
    
    def force_close(self):
        """用于程序成功更新之后跳过确认窗口强制关闭。"""
        self.forceCloseFlag = True
        self.close()

    def check_if_can_close(self):
        closeChecked = False
        if self.managePage.exitmodePanel.mountoutDialog.countoutMode and not self.managePage.exitmodePanel.mountoutDialog.remainMount or self.managePage.exitmodePanel.timeoutDialog.timeoutFlag or self.forceCloseFlag:
            closeChecked = True
        else:
            if threading.active_count() == 1:
                closeChecked = True
            else:
                self.show()
                ok = QMessageBox.question(self, '退出', '当前有下载任务正在进行,您是否要挂起全部下载任务并退出?',QMessageBox.Cancel|QMessageBox.Ok, QMessageBox.Cancel )
                if ok == QMessageBox.Ok:
                    closeChecked = True
                else:
                    closeChecked = False
        return closeChecked

    def closeEvent(self, event):
        if self.check_if_can_close():
            self.handle_before_close()
            event.accept()  
        else:
            event.ignore()

    def handle_before_close(self):
        self.playbackPanel.stop_music()
        self.hide()
        self.trayIcon.hide()
        self.managePage.handle_before_app_exit()
        for t in threading.enumerate():
            if t.name == 'downloadLrc':
                t.stop()
            elif t!= threading.main_thread():
                t.pause()
                t.join()

    def playmode_changed(self, oldPlaymode, newPlaymode):
        self.playmodeActions[oldPlaymode].setIcon(QIcon(IconsHub.PlaymodeSelectedNo))
        self.playmodeActions[newPlaymode].setIcon(QIcon(IconsHub.PlaymodeSelected))
    
    def playmode_order_seted(self):
        if self.playbackPanel.playmode != Configures.PlaymodeOrder:
            self.playmode_changed(self.playbackPanel.playmode, Configures.PlaymodeOrder)
            self.playbackPanel.set_new_playmode(Configures.PlaymodeOrder)
    
    def playmode_random_seted(self):
        if self.playbackPanel.playmode != Configures.PlaymodeRandom:
            self.playmode_changed(self.playbackPanel.playmode, Configures.PlaymodeRandom)
            self.playbackPanel.set_new_playmode(Configures.PlaymodeRandom)
    
    def playmode_single_seted(self):
        if self.playbackPanel.playmode != Configures.PlaymodeSingle:
            self.playmode_changed(self.playbackPanel.playmode, Configures.PlaymodeSingle)
            self.playbackPanel.set_new_playmode(Configures.PlaymodeSingle)
    
    def show_minimized(self):
        self.showMinimized()

    def show_mainwindow(self):
        if self.isHidden():
            self.showMainWindowAction.setText('隐藏主界面')
            self.show()
        else:
            self.showMainWindowAction.setText('显示主界面')
            self.hide()
    
    def dont_hide_mainwindow(self):
        if self.isHidden():
            self.showMainWindowAction.setText('隐藏主界面')
            self.show()        
    
    def ui_initial(self):
        self.playbackPanel.ui_initial()
        self.managePage.ui_initial()
    
    def show_lyric_text(self):
        self.pageLabel.hide()
        self.mainPageButton.hide()
        self.managePage.show_main_stack_window()
    
    def show_about_page(self):
        self.show_page_label_and_button(self.tr('关于'))
        self.managePage.show_about_page()
    
    def show_settings_frame(self):
        self.show_page_label_and_button(self.tr('选项'))
        self.managePage.show_settings_frame()
    
    def search_musics_online(self, keyword):
        if self.managePage.searchFrame.search_musics(keyword):
            self.show_page_label_and_button(self.tr('搜索'))
            self.managePage.show_search_frame()

    def show_artist_info(self, name):
        self.show_page_label_and_button(self.tr('歌手'))
        self.managePage.show_artist_info(name)
    
    def show_song_info(self, row):
        self.show_page_label_and_button(self.tr('歌曲'))
        self.managePage.show_song_info(row)

    def hide_song_info_page(self, arg1=None, arg2=None):
        if self.managePage.get_page_description() == '歌曲':
            self.show_lyric_text()

    def show_page_label_and_button(self, pageName):
        self.pageLabel.setText(self.tr(pageName))
        self.mainPageButton.show()
        self.pageLabel.show()
    
    def close_button_acted(self):
        if self.closeButtonAct == Configures.SettingsHide:
            self.show_mainwindow()
        else:
            self.close()