Example #1
0
class SideBar(QWidget):
    def __init__(self, manager, window):
        super().__init__(window)
        self._window = window  # BrowserWindow
        self._layout = None  # QVBoxLayout
        self._titleBar = None  # DockTitleBarWidget
        self._manager = manager  # SideBarManager

        self.setObjectName('sidebar')
        self.setAttribute(Qt.WA_DeleteOnClose)

        self._layout = QVBoxLayout(self)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self._layout.setSpacing(0)
        self.setLayout(self._layout)

        self._titleBar = DockTitleBarWidget('', self)
        self._layout.addWidget(self._titleBar)

    def showBookmarks(self):
        self._titleBar.setTitle(_('Bookmarks'))
        bar = BookmarksSideBar(self._window)
        self.setWidget(bar)

    def showHistory(self):
        self._titleBar.setTitle(_('History'))
        bar = HistorySideBar(self._window)
        self.setWidget(bar)

    def setTitle(self, title):
        self._titleBar.setTitle(title)

    def setWidget(self, widget):
        if self._layout.count() == 2:
            self._layout.removeItem(self._layout.itemAt(1))

        if widget:
            self._layout.addWidget(widget)

    # Q_SLOTS
    def close(self):
        self._manager.closeSideBar()

        p = self.parentWidget()
        if p:
            p.setFocus()

        super().close()
Example #2
0
class PlayWidget(QWidget):
    def __init__(self, parent):
        super(PlayWidget, self).__init__(parent)
        self.setAutoFillBackground(True)
        self.hlayout = QHBoxLayout(self)

        self.table_view = CardView(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.table_view.sizePolicy().hasHeightForWidth())
        self.table_view.setSizePolicy(sizePolicy)
        self.table_view.setMinimumHeight(200)
        self.table_view.setBackgroundBrush(Qt.darkGreen)
        self.table_view.setGeometry(0, 0, 1028, 200)

        self.hand_view = HandCardView(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.hand_view.sizePolicy().hasHeightForWidth())
        self.hand_view.setSizePolicy(sizePolicy)
        self.hand_view.setMinimumHeight(200)
        self.hand_view.setBackgroundBrush(Qt.darkGreen)
        self.hand_view.setGeometry(0, 0, 1028, 200)

        self.show_button = Button(self, 'Show Hand')
        self.show_button.setText("Show hand")
        self.show_button.clicked.connect(self.hand_view.show_cards)
        self.show_button.hide()

        self.move_button = Button(self, 'Make Move')
        self.move_button.setMinimumSize(300, 100)
        self.move_button.clicked.connect(self.attempt_move)
        self.move_button.hide()

        self.start_button = Button(self, 'Start Round')
        self.start_button.setMinimumHeight(100)
        self.start_button.clicked.connect(self.start_round)

        self.next_button = Button(self, 'Continue')
        self.next_button.setMinimumHeight(100)
        self.next_button.clicked.connect(self.goto_next_round)
        self.next_button.hide()

        self.quit_button = Button(self, 'Quit to menu')

        self.save_button = Button(self, 'Save')

        self.show_button.setMaximumWidth(150)
        self.move_button.setMaximumWidth(150)
        self.quit_button.setMaximumWidth(150)

        self.btnlayout = QHBoxLayout()
        self.btnlayout.addWidget(self.start_button)

        self.btn2layout = QHBoxLayout()
        self.btn2layout.addWidget(self.save_button)
        self.btn2layout.addWidget(self.quit_button)

        self.playlayout = QVBoxLayout()
        self.playlayout.addWidget(self.table_view)
        self.playlayout.addLayout(self.btnlayout)
        self.playlayout.addWidget(self.hand_view)
        self.playlayout.addLayout(self.btn2layout)
        self.hlayout.addLayout(self.playlayout)

        self.sidelayout = QVBoxLayout()
        self.log = QPlainTextEdit()
        self.log.setReadOnly(True)
        self.log.setPalette(QPalette(Qt.white))
        self.log.setMaximumWidth(300)
        self.log.setMaximumHeight(200)
        self.sidelayout.addWidget(self.log)

        self.playerinfolayout = QVBoxLayout()
        self.sidelayout.addLayout(self.playerinfolayout)

        self.sidelayout.addItem(
            QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))

        self.hlayout.addLayout(self.sidelayout)

        self.setup_sound()

        self.move_count = 0
        self.speed = 3
        self.game = None

    def init_game(self, game):
        self.game = game
        self.game.logSignal.connect(self.update_log)
        self.game.sweepSignal.connect(self.sweep_sound.play)

        self.game.new_round()
        self.shuffle_sound.play()
        self.game.initial_deal()

        self.move_count = 0

        for player in self.game.players:
            self.playerinfolayout.addWidget(PlayerInfo(self, player))

    def start_round(self):
        self.btnlayout.removeWidget(self.start_button)
        self.btnlayout.insertWidget(0, self.show_button)
        self.btnlayout.insertWidget(1, self.move_button)
        self.start_button.hide()
        self.show_button.show()
        self.move_button.show()

        self.table_view.update_scene(self.game.table)
        self.hand_view.update_scene(self.game.current_player.hand)

        self.hand_view.hide_cards()

        if type(self.game.current_player
                ) is not AIPlayer and self.game.one_human:
            self.hand_view.show_cards()

        self.playerinfolayout.itemAt(
            self.game.players.index(
                self.game.current_player)).widget().set_active()
        self.update_log('\n----------\n{}\'s turn\n'.format(
            self.game.current_player))

        if type(self.game.current_player) is AIPlayer:
            self.move_button.setDisabled(True)
            self.show_button.setDisabled(True)
            self.save_button.setDisabled(True)
            self.make_ai_move()

    def resume_from_save(self, game, logmsg, movecount):
        self.game = game
        self.game.logSignal.connect(self.update_log)
        self.game.sweepSignal.connect(self.sweep_sound.play)

        self.log.insertPlainText(logmsg)
        self.log.insertPlainText(
            '\n----------------\n    Resuming from save\n----------------\n\n')

        self.move_count = movecount

        for player in self.game.players:
            self.playerinfolayout.addWidget(PlayerInfo(self, player))

        self.playerinfolayout.itemAt(
            self.game.players.index(
                self.game.current_player)).widget().set_active()

    def make_ai_move(self):
        self.game.select_move_for_ai()
        QTimer.singleShot(1500 // self.speed, self.show_ai_move)

    def show_ai_move(self):
        self.hand_view.auto_select()
        self.table_view.auto_select()
        self.card_sound.play()
        self.game.do_action()
        QTimer.singleShot(3000 // self.speed, self.after_ai_move_done)

    def after_ai_move_done(self):
        self.move_sound.play()
        self.playerinfolayout.itemAt(
            self.game.players.index(
                self.game.current_player)).widget().update_info()
        self.game.deal()
        self.table_view.update_scene(self.game.table)
        self.hand_view.update_scene(self.game.current_player.hand)
        self.hand_view.hide_cards()
        QTimer.singleShot(3000 // self.speed, self.end_turn)

    def attempt_move(self):
        if self.game.do_action():
            self.move_sound.play()
            self.playerinfolayout.itemAt(
                self.game.players.index(
                    self.game.current_player)).widget().update_info()
            self.move_button.setDisabled(True)
            self.table_view.update_scene(self.game.table)
            self.hand_view.update_scene(self.game.current_player.hand)
            QTimer.singleShot(1800 // self.speed, self.after_move_done)

        else:
            self.error_sound.play()

    def after_move_done(self):
        self.game.deal()
        self.hand_view.update_scene(self.game.current_player.hand)
        QTimer.singleShot(3000 // self.speed, self.end_turn)

    def end_turn(self):
        self.playerinfolayout.itemAt(
            self.game.players.index(
                self.game.current_player)).widget().set_inactive()
        self.game.next_player()
        self.playerinfolayout.itemAt(
            self.game.players.index(
                self.game.current_player)).widget().set_active()

        self.move_button.setDisabled(False)
        self.show_button.setDisabled(False)
        self.table_view.deselect_all()

        self.move_count += 1
        if self.move_count == 48:
            self.end_round()
            return

        self.update_log('\n----------\n{}\'s turn\n'.format(
            self.game.current_player))
        self.hand_view.update_scene(self.game.current_player.hand)
        self.hand_view.hide_cards()

        #if there is only one human player, his/her cards are shown automatically
        if type(self.game.current_player
                ) is not AIPlayer and self.game.one_human:
            self.hand_view.show_cards()
            self.alert_sound.play()

        if type(self.game.current_player) is AIPlayer:
            self.move_button.setDisabled(True)
            self.show_button.setDisabled(True)
            self.save_button.setDisabled(True)
            self.make_ai_move()
            return

        self.save_button.setDisabled(False)

    def end_round(self):
        self.save_button.setDisabled(True)
        self.playerinfolayout.itemAt(
            self.game.players.index(
                self.game.current_player)).widget().set_inactive()
        self.end_sound.play()
        game_ended = self.game.end_round()
        for i in range(self.playerinfolayout.count()):
            self.playerinfolayout.itemAt(i).widget().update_info()
            self.playerinfolayout.itemAt(i).widget().update_score()

        self.table_view.update_scene(self.game.table)

        self.btnlayout.removeWidget(self.show_button)
        self.btnlayout.removeWidget(self.move_button)
        self.btnlayout.insertWidget(0, self.next_button)
        self.next_button.show()
        self.show_button.hide()
        self.move_button.hide()
        if game_ended:
            self.next_button.setDisabled(True)

    def goto_next_round(self):
        self.save_button.setDisabled(False)
        self.btnlayout.removeWidget(self.next_button)
        self.btnlayout.insertWidget(0, self.start_button)
        self.start_button.show()
        self.next_button.hide()

        #rotate playerinfo
        mov = self.playerinfolayout.itemAt(0).widget()
        self.playerinfolayout.removeWidget(mov)
        self.playerinfolayout.addWidget(mov)

        self.game.new_round()
        self.shuffle_sound.play()

        for i in range(self.playerinfolayout.count()):
            self.playerinfolayout.itemAt(i).widget().update_info()

        self.game.new_round()
        self.game.initial_deal()

        self.move_count = 0

    def setup_sound(self):
        self.shuffle_sound = QSoundEffect()
        self.shuffle_sound.setSource(QUrl.fromLocalFile('sound/shuffle.wav'))

        self.error_sound = QSoundEffect()
        self.error_sound.setSource(QUrl.fromLocalFile('sound/error.wav'))

        self.move_sound = QSoundEffect()
        self.move_sound.setSource(QUrl.fromLocalFile('sound/draw.wav'))

        self.card_sound = QSoundEffect()
        self.card_sound.setSource(QUrl.fromLocalFile('sound/playcard.wav'))

        self.sweep_sound = QSoundEffect()
        self.sweep_sound.setSource(QUrl.fromLocalFile('sound/sweep.wav'))

        self.alert_sound = QSoundEffect()
        self.alert_sound.setSource(QUrl.fromLocalFile('sound/alert.wav'))

        self.end_sound = QSoundEffect()
        self.end_sound.setSource(QUrl.fromLocalFile('sound/endturn.wav'))

    def reset(self):
        self.game = None

    def update_log(self, msg):
        self.log.insertPlainText(msg)
        self.log.ensureCursorVisible()  #auto-scrolls to bottom of log

    def export_log(self):
        return self.log.toPlainText()
Example #3
0
class StartMenu(QWidget):
    
    def __init__(self, parent):
        super(StartMenu, self).__init__(parent)
        
        self.tophbox = QHBoxLayout()
        self.hbox = QHBoxLayout()
        self.vbox = QVBoxLayout()
        
        self.label = QLabel()
        self.label.setPixmap(QPixmap('img/new-game.png'))
        self.label.setScaledContents(True)
        self.label.setFixedSize(600, 200)
        self.tophbox.addWidget(self.label)
        
        self.startbutton = Button(self, 'Start')
        self.startbutton.setEnabled(False)
        self.startbutton.setFixedHeight(100)
        self.tophbox.addWidget(self.startbutton)
        
        self.playeramt_label = QLabel('Number of players:')
        self.playeramt_label.setFixedWidth(125)
        
        self.playeramount = QComboBox()
        self.playeramount.setStyleSheet('color: rgb(0, 0, 0)')
        self.playeramount.setFixedWidth(50)
        self.playeramount.addItems([str(i) for i in range(2, 13)])
        self.playeramount.setCurrentIndex(2)
        self.playeramount.setMaxVisibleItems(11)
        self.playeramount.currentTextChanged.connect(self.form_player_entries)
        
        self.score_label = QLabel('Score limit:')
        self.score_label.setFixedWidth(65)
        
        self.score_limit = QLineEdit()
        self.score_limit.setMaximumWidth(40)
        self.score_limit.setPalette(QPalette(Qt.white))
        self.score_limit.setText('16')
        
        self.mode_label = QLabel('Game Mode:')
        self.mode_label.setFixedWidth(85)
        
        self.mode_select = QComboBox()
        self.mode_select.addItems(['Deal-1', 'Deal-4'])
        self.mode_select.setPalette(QPalette(Qt.white))
        self.mode_select.setFixedWidth(100)
        self.mode_select.currentTextChanged.connect(self.update_playeramount)
            
        self.autofill_button = Button(self, 'Auto Fill')
        self.autofill_button.clicked.connect(self.auto_fill)
        self.clear_button = Button(self, 'Clear All')
        self.clear_button.clicked.connect(self.clear_all)
        
        self.player_entries = QVBoxLayout()
        
        self.spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
        
        self.hbox.addWidget(self.playeramt_label)
        self.hbox.addWidget(self.playeramount)
        self.hbox.addWidget(self.score_label)
        self.hbox.addWidget(self.score_limit)
        self.hbox.addWidget(self.mode_label)
        self.hbox.addWidget(self.mode_select)
        self.hbox.addWidget(self.autofill_button)
        self.hbox.addWidget(self.clear_button)
        
        self.vbox.addLayout(self.tophbox)
        self.vbox.addLayout(self.hbox)
        self.vbox.addLayout(self.player_entries)
        self.vbox.addItem(self.spacer)
        
        self.setLayout(self.vbox)
        
        self.form_player_entries() 


    def form_player_entries(self):
        amt = self.playeramount.currentIndex() + 2  #desired amount of player entries
        curr = self.player_entries.count()          #current amount of player entries
        dif = amt - self.player_entries.count()     #difference between desired and current entries
        
        if dif < 0:                                 #if too many entries currently 
            for i in range(curr-1, amt-1, -1):      #remove starting from last entry
                rm = self.player_entries.itemAt(i).widget()
                self.player_entries.removeWidget(rm)
                rm.setParent(None)
                
        
        else:                                       #if too few entries, add until desired amount reached
            for i in range(dif):
                new_entry = PlayerInfoField(self, self.player_entries.count())
                new_entry.name_field.textChanged.connect(self.check_filled)
                self.player_entries.addWidget(new_entry)
         
        self.check_filled()       
    
    
    def check_filled(self): #Enables start button when player fields are correctly filled
        ready = True
        
        for i in range(self.player_entries.count()):
            entry = self.player_entries.itemAt(i).widget()
            if entry.name_field.text() == '':
                ready = False
                break
            
        if ready:
            self.startbutton.setEnabled(True)
        else:
            self.startbutton.setEnabled(False)
            
                
    def auto_fill(self): #Generates fills the rest of the form automatically
        for i in range(self.player_entries.count()):
            entry = self.player_entries.itemAt(i).widget()
            if entry.name_field.text() == '':
                entry.generate_name()
                entry.AItoggle.setChecked(True)
                
    def clear_all(self):
        for i in range(self.player_entries.count()):
            entry = self.player_entries.itemAt(i).widget()
            entry.name_field.clear()
            entry.AItoggle.setChecked(False)
            
    def update_playeramount(self, mode):
        ind = self.playeramount.currentIndex()
        if mode == 'Deal-1': #Limit max players to 12
            self.playeramount.clear()
            self.playeramount.addItems([str(i) for i in range(2, 13)])
            self.playeramount.setCurrentIndex(ind)
            
        if mode == 'Deal-4': #Limit max players to 4
            self.playeramount.clear()
            self.playeramount.addItems([str(i) for i in range(2, 5)])
            self.playeramount.setCurrentIndex(min(2, ind))
            
        self.check_filled()
            
                
    def extract_info_and_init_game(self): #Creates a game object based on the info 
        pointlimit = int(self.score_limit.text())
        dealmode = self.mode_select.currentIndex()
        game = Game(pointlimit, dealmode)
        
        for i in range(self.player_entries.count()):
            entry = self.player_entries.itemAt(i).widget()
            name = entry.name_field.text()
            if entry.AItoggle.isChecked():
                difficulty = entry.AIdifficulty.currentIndex()
                game.add_player(name, difficulty)
            else:
                game.add_player(name, 5)
            
        return game
Example #4
0
class WebTab(QWidget):

    class SavedTab(object):
        def __init__(self, webTab=None):
            self.title = ''
            self.url = QUrl()
            self.icon = QIcon()
            self.history = QByteArray()
            self.isPinned = False
            self.zoomLevel = 1
            self.parentTab = -1
            self.childTabs = []
            self.sessionData = {}
            if webTab:
                self.setWebTab(webTab)

        def __getstate__(self):
            result = dict(self.__dict__)
            result['url'] = result['url'].toEncoded()
            data = QByteArray()
            ds = QDataStream(data, QIODevice.WriteOnly)
            ds.writeQVariant(self.icon)
            result['icon'] = data.data()
            return result

        def __setstate__(self, state):
            for key, val in state.items():
                if key == 'url':
                    self.__dict__[key] = QUrl.fromEncoded(val)
                elif key == 'icon':
                    ds = QDataStream(QByteArray(val))
                    self.__dict__[key] = ds.readQVariant()
                else:
                    self.__dict__[key] = val

        def setWebTab(self, webTab):
            self.title = webTab.title()
            self.url = webTab.url()
            self.icon = webTab.icon()
            self.history = webTab.historyData()
            self.isPinned = webTab.isPinned()
            self.zoomLevel = webTab.zoomLevel()
            if webTab.parentTab():
                self.parentTab = webTab.parentTab().tabIndex()
            else:
                self.parentTab = -1
            self.childTabs = [ tab.tabIndex() for tab in webTab.childTabs() ]
            self.sessionData = webTab.sessionData()

        def isValid(self):
            return not self.url.isEmpty() or not self.history.isEmpty()

        def clear(self):
            self.title = ''
            self.url = QUrl()
            self.icon = QIcon()
            self.history = QByteArray()
            self.isPinned = False
            self.zoomLevel = 1
            self.parentTab = -1
            self.childTabs = []
            self.sessionData = {}

    # type AddChildBehavior
    AppendChild = 0
    PrependChild = 1

    s_addChildBehavior = AppendChild

    def __init__(self, parent=None):
        super(WebTab, self).__init__(parent)
        self.setObjectName('webtab')

        self._tabBar = None
        self._window = None
        self._parentTab = None
        self._childTabs = []
        self._sessionData = {}
        self._savedTab = self.SavedTab()
        self._isPinned = False
        self._isCurrentTab = False

        self._webView = TabbedWebView(self)
        self._webView.setPage(WebPage())
        self._webView.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        self.setFocusProxy(self._webView)

        self._locationBar = LocationBar(self)
        self._locationBar.setWebView(self._webView)

        self._tabIcon = TabIcon(self)
        self._tabIcon.setWebTab(self)

        self._layout = QVBoxLayout(self)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self._layout.setSpacing(0)
        self._layout.addWidget(self._webView)

        viewWidget = QWidget(self)
        viewWidget.setLayout(self._layout)

        self._splitter = QSplitter(Qt.Vertical, self)
        self._splitter.setChildrenCollapsible(False)
        self._splitter.addWidget(viewWidget)

        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self._splitter)
        self.setLayout(layout)

        self._notificationWidget = QWidget(self)
        self._notificationWidget.setAutoFillBackground(True)
        pal = self._notificationWidget.palette()
        pal.setColor(QPalette.Window, pal.window().color().darker(110))
        self._notificationWidget.setPalette(pal)

        nlayout = QVBoxLayout(self._notificationWidget)
        nlayout.setSizeConstraint(QLayout.SetMinAndMaxSize)
        nlayout.setContentsMargins(0, 0, 0, 0)
        nlayout.setSpacing(1)

        self._webView.showNotification.connect(self.showNotification)
        self._webView.loadFinished.connect(self.loadFinished)
        self._webView.titleChanged.connect(self.titleWasChanged)
        self._webView.titleChanged.connect(self.titleChanged)
        self._webView.iconChanged.connect(self.iconChanged)

        self._webView.backgroundActivityChanged.connect(self.backgroundActivityChanged)
        self._webView.loadStarted.connect(lambda: self.loadingChanged.emit(True))
        self._webView.loadFinished.connect(lambda: self.loadingChanged.emit(False))

        def pageChanged(page):
            page.audioMutedChanged.connect(self.playingChanged)
            page.recentlyAudibleChanged.connect(self.mutedChanged)

        pageChanged(self._webView.page())
        self._webView.pageChanged.connect(pageChanged)

        def tabIconResized():
            if self._tabBar:
                self._tabBar.update()
        self._tabIcon.resized.connect(tabIconResized)

    def browserWindow(self):
        '''
        @return BrowserWindow
        '''
        return self._window

    def webView(self):
        '''
        @return TabbedWebView
        '''
        return self._webView

    def locationBar(self):
        '''
        @return LocationBar
        '''
        return self._locationBar

    def tabIcon(self):
        '''
        @return TabIcon
        '''
        return self._tabIcon

    def parentTab(self):
        '''
        @return WebTab
        '''
        return self._parentTab

    def setParentTab(self, tab):
        if self._isPinned or self._parentTab == tab:
            return
        if tab and tab.isPinned():
            return

        if self._parentTab:
            index = self._parentTab._childTabs.index(self)
            if index >= 0:
                self._parentTab._childTabs.pop(index)
                self._parentTab.childTabRemoved.emit(self, index)
        self._parentTab = tab

        if tab:
            self._parentTab = None
            tab.addChildTab(self)
        else:
            self.parentTabChanged.emit(self._parentTab)

    def addChildTab(self, tab, index=-1):
        if self._isPinned or not tab or tab.isPinned():
            return

        oldParent = tab._parentTab
        tab._parentTab = self
        if oldParent:
            index = oldParent._childTabs.index(tab)
            if index >= 0:
                oldParent._childTabs.pop(index)
                oldParent.childTabRemoved.emit(tab, index)

        if index < 0 or index > len(self._childTabs):
            index = 0
            if self.addChildBehavior() == self.AppendChild:
                index = len(self._childTabs)
            else:  # PrependChild
                index = 0

        self._childTabs.insert(index, tab)
        self.childTabAdded.emit(tab, index)
        tab.parentTabChanged.emit(self)

    def childTabs(self):
        '''
        @return QVector<WebTab*>
        '''
        return self._childTabs

    def sessionData(self):
        '''
        @return {}
        '''
        return self._sessionData

    def setSessionData(self, key, value):
        self._sessionData[key] = value

    def url(self):
        if self.isRestored():
            if self._webView.url().isEmpty() and self._webView.isLoading():
                return self._webView.page().requestedUrl()
            return self._webView.url()
        else:
            return self._savedTab.url

    def title(self, allowEmpty=False):
        if self.isRestored():
            return self._webView.title(allowEmpty)
        else:
            return self._savedTab.title

    def icon(self, allowNull=False):
        if self.isRestored():
            return self._webView.icon(allowNull)
        if allowNull or not self._savedTab.icon.isNull():
            return self._savedTab.icon
        return IconProvider.emptyWebIcon()

    def history(self):
        '''
        @return QWebEngineHistory
        '''
        return self._webView.history()

    def zoomLevel(self):
        return self._webView.zoomLevel()

    def setZoomLevel(self, level):
        self._webView.setZoomLevel(level)

    def detach(self):
        assert(self._window)
        assert(self._tabBar)

        # Remove from tab tree
        self.removeFromTabTree()

        # Remove icon from tab
        self._tabBar.setTabButton(self.tabIndex(), self._tabBar.iconButtonPosition(), None)
        self._tabIcon.setParent(self)

        # Remove the tab from tabbar
        self._window.tabWidget().removeTab(self.tabIndex())
        self.setParent(None)
        # Remove the locationbar from window
        self._locationBar.setParent(self)
        # Detach TabbedWindow
        self._webView.setBrowserWindow(None)

        if self._isCurrentTab:
            self._isCurrentTab = False
            self.currentTabChanged.emit(self._isCurrentTab)

        self._tabBar.currentChanged.disconnect(self.onCurrentChanged)

        self._window = None
        self._tabBar = None

    def onCurrentChanged(self, index):
        wasCurrent = self._isCurrentTab
        self._isCurrentTab = index == self.tabIndex()
        if wasCurrent != self._isCurrentTab:
            self.currentTabChanged.emit(self._isCurrentTab)

    def attach(self, window):
        self._window = window
        self._tabBar = self._window.tabWidget().tabBar()

        self._webView.setBrowserWindow(self._window)
        self._locationBar.setBrowserWindow(self._window)
        self._tabBar.setTabText(self.tabIndex(), self.title())
        self._tabBar.setTabButton(self.tabIndex(), self._tabBar.iconButtonPosition(), self._tabIcon)
        QTimer.singleShot(0, self._tabIcon.updateIcon)

        self.onCurrentChanged(self._tabBar.currentIndex())
        self._tabBar.currentChanged.connect(self.onCurrentChanged)

    def historyData(self):
        '''
        @return QByteArray
        '''
        if self.isRestored():
            historyArray = QByteArray()
            stream = QDataStream(historyArray, QIODevice.WriteOnly)
            history = self._webView.history()
            stream << history
            return historyArray
        else:
            return self._savedTab.history

    def stop(self):
        self._webView.stop()

    def reload(self):
        self._webView.reload()

    def load(self, request):
        '''
        @param: requset LoadRequest
        '''
        if self.isRestored():
            self.tabActivated()
            QTimer.singleShot(0, lambda: self.load(request))
        else:
            self._webView.load(request)

    def unload(self):
        self._savedTab = self.SavedTab(self)
        self.restoredChanged.emit(self.isRestored())
        self._webView.setPage(WebPage())
        self._webView.setFocus()

    def isLoading(self):
        return self._webView.isloading()

    def isPinned(self):
        return self._isPinned

    def setPinned(self, state):
        if self._isPinned == state:
            return
        if state:
            self.removeFromTabTree()
        self._isPinned = state
        self.pinnedChanged.emit(self._isPinned)

    def togglePinned(self):
        assert(self._tabBar)
        assert(self._window)
        self.setPinned(not self.isPinned())
        self._window.tabWidget().pinUnPinTab(self.tabIndex(), self.title())

    def isMuted(self):
        return self._webView.page().isAudioMuted()

    def isPlaying(self):
        return self._webView.page().recentlyAudible()

    def setMuted(self, muted):
        self._webView.page().setAudioMuted(muted)

    def toggleMuted(self):
        self.setMuted(not self.isMuted())

    def backgroundActivity(self):
        return self._webView.backgroundActivity()

    def tabIndex(self):
        index = -1
        if self._tabBar:
            index = self._tabBar.tabWidget().indexOf(self)
        return index

    def isCurrentTab(self):
        return self._isCurrentTab

    def makeCurrentTab(self):
        if self._tabBar:
            self._tabBar.tabWidget().setCurrentIndex(self.tabIndex())

    def closeTab(self):
        if self._tabBar:
            self._tabBar.tabWidget().closeTab(self.tabIndex())

    def moveTab(self, to):
        if self._tabBar:
            self._tabBar.tabWidget().moveTab(self.tabIndex(), to)

    def haveInspector(self):
        return self._splitter.count() > 1 and self._splitter.widget(1).inherits('WebInspector')

    def showWebInspector(self, inspectElement=False):
        if not WebInspector.isEnabled() or self.haveInspector():
            return

        inspector = WebInspector(self)
        inspector.setView(self._webView)
        if inspectElement:
            inspector.inspectElement()

        height = inspector.sizeHint().height()
        self._splitter.addWidget(inspector)
        self._splitter.setSizes((self._splitter.height() - height, height))

    def toggleWebInspector(self):
        if not self.haveInspector():
            self.showWebInspector()
        else:
            self._splitter.widget(1).destroy()  # TODO: del?

    def showSearchToolBar(self, searchText=''):
        index = 1
        toolBar = None
        if self._layout.count() == 1:
            toolBar = SearchToolBar(self._webView, self)
            self._layout.insertWidget(index, toolBar)
        if self._layout.count() == 2:
            assert(isinstance(self._layout.itemAt(index).widget(), SearchToolBar))
            toolBar = self._layout.itemAt(index).widget()
        assert(toolBar)
        if not searchText:
            toolBar.setText(searchText)
        toolBar.focusSearchLine()

    def isRestored(self):
        return not self._savedTab.isValid()

    def restoreTab(self, tab):
        '''
        @param: tab SavedTab
        '''
        assert(self._tabBar)
        self.setPinned(tab.isPinned)
        self._sessionData = tab.sessionData

        if not self.isPinned() and gVar.appSettings.loadTabsOnActivation:
            self._savedTab = tab
            self.restoredChanged.emit(self.isRestored())
            index = self.tabIndex()

            self._tabBar.setTabText(index, tab.title)
            self._locationBar.showUrl(tab.url)
            self._tabIcon.updateIcon()
        else:
            # This is called only on restore session and restoring tabs
            # immediately crashes QtWebEngine, waiting after initialization is
            # complete fixes it
            QTimer.singleShot(1000, lambda: self.p_restoreTab(tab))

    def p_restoreTab(self, tab):
        '''
        @param: tab SavedTab
        '''
        self.p_restoreTabByUrl(tab.url, tab.history, tab.zoomLevel)

    def p_restoreTabByUrl(self, url, history, zoomLevel):
        self._webView.load(url)

        # Restoring history of internal pages crashes QtWebEngine 5.8
        blacklistedSchemes = ['view-source', 'chrome']

        if (url.scheme() not in blacklistedSchemes):
            stream = QDataStream(history)
            stream >> self._webView.history()

        self._webView.setZoomLevel(zoomLevel)
        self._webView.setFocus()

    def tabActivated(self):
        if self.isRestored():
            return

        def _onTabActivated():
            if self.isRestored():
                return
            self.p_restoreTab(self._savedTab)
            self._savedTab.clear()
            self.restoredChanged.emit(self.isRestored())

        QTimer.singleShot(0, _onTabActivated)

    def addChildBehavior(self):
        '''
        @return AddChildBehavior
        '''
        return self.s_addChildBehavior

    def setAddChildBehavior(self, behavior):
        self.s_addChildBehavior = behavior

    # Q_SLOTS
    @pyqtSlot(QWidget)
    def showNotification(self, notif):
        self._notificationWidget.setParent(self)
        self._notificationWidget.raise_()
        self._notificationWidget.setFixedWidth(self.width())
        self._notificationWidget.layout().addWidget(notif)
        self._notificationWidget.show()
        notif.show()

    @pyqtSlot()
    def loadFinished(self):
        self.titleWasChanged(self._webView.title())

    # Q_SIGNALS
    titleChanged = pyqtSignal(str) # title
    iconChanged = pyqtSignal(QIcon) # icon
    pinnedChanged = pyqtSignal(bool) # pinned
    restoredChanged = pyqtSignal(bool) # restored
    currentTabChanged = pyqtSignal(bool) # current
    loadingChanged = pyqtSignal(bool) # loading
    mutedChanged = pyqtSignal(bool) # muted
    playingChanged = pyqtSignal(bool) # playing
    backgroundActivityChanged = pyqtSignal(bool) # activity
    parentTabChanged = pyqtSignal('PyQt_PyObject') # WebTab*
    childTabAdded = pyqtSignal('PyQt_PyObject', int) # WebTab*, index
    childTabRemoved = pyqtSignal('PyQt_PyObject', int) # WebTab*, index

    def titleWasChanged(self, title):
        if not self._tabBar or not self._window or not title:
            return
        if self._isCurrentTab:
            self._window.setWindowTitle('%s - Demo' % title)
        self._tabBar.setTabText(self.tabIndex(), title)

    # override
    def resizeEvent(self, event):
        QWidget.resizeEvent(self, event)
        self._notificationWidget.setFixedWidth(self.width())

    def removeFromTabTree(self):
        parentTab = self._parentTab
        parentIndex = -1
        if parentTab:
            parentIndex = parentTab._childTabs.index(self)
        self.setParentTab(None)

        idx = 0
        while self._childTabs:
            child = self._childTabs[0]
            child.setParentTab(None)
            if parentTab:
                parentTab.addChildTab(child, parentIndex + idx)
                idx += 1