def on_selection_changed(self, icon_view, album=None):
        popup = Popover.new(self.albumview)
        popup.set_size_request(810, 240)

        if album is None:
            selection = icon_view.get_selected_items()
            if len(selection) != 1:
                return

            path = selection[0]
            treeiter = self.albumfilter.get_iter(path)

            isset, path, cell = icon_view.get_cursor()
            isset, rect = icon_view.get_cell_rect(path, cell)
            popup.set_pointing_to(rect)

            album_id = self.albumfilter.get_value(treeiter, 4)
            album_obj = self.albums[album_id]
        else:
            album_obj = album
            popup.set_relative_to(self.search_entry)

        # Handle double clicks
        def empty_dblclick():
            self.dblclick = None

        if self.dblclick is None:
            self.dblclick = album_obj
            timeout_add(1000, empty_dblclick)
        elif self.dblclick == album_obj:
            self.play(album_obj)
            return

        album = album_obj.name
        artist = album_obj.artist

        glade_album = join(self.functions.datadir, 'glade', 'albumview.ui')
        box = gtk_builder()
        box.set_translation_domain('bluemindo')
        box.add_from_file(glade_album)
        popup.add(box.get_object('box1'))

        box.get_object('label_album').set_text(album)
        box.get_object('label_artist').set_text(artist)

        bdir = join(self.userconf.datadir, 'modules', 'player', 'covers')
        cover = join(bdir, self.functions.get_hash(album, artist))
        if isfile(cover):
            cover_px = Pixbuf.new_from_file_at_scale(cover, 180, 180, True)
        else:
            cover_px = Pixbuf.new_from_file(join(self.functions.datadir,
                                            'image', 'logo_head_big.png'))

        box.get_object('album_cover').set_from_pixbuf(cover_px)

        def play_album(wdg, album):
            self.play(album)

        def queue_album(wdg, album):
            self.queue(album)

        def change_cover(wdg, ka, album):
            artist_name = album.artist
            album_name = album.name

            fcdialog = FileChooserDialog(
                        title=_('Change the cover picture for this album'),
                        buttons=(_('Select'), ResponseType.OK))

            fcdialog.set_transient_for(self.widgets[0][11])
            response = fcdialog.run()
            if response == ResponseType.OK:
                filename = fcdialog.get_filename()

                datadir = self.userconf.datadir
                hash_a = self.functions.get_hash(album_name, artist_name)
                pictures_dir = join(datadir, 'modules', 'player', 'covers')
                album_file = join(pictures_dir, hash_a)

                copyfile(filename, album_file)

                new = Pixbuf.new_from_file_at_scale(album_file, 180, 180, True)
                box.get_object('album_cover').set_from_pixbuf(new)

            fcdialog.destroy()

        box.get_object('button_play').connect('clicked', play_album, album_obj)

        box.get_object('button_add').connect('clicked', queue_album, album_obj)

        box.get_object('coverevent').connect('button-press-event',
                                             change_cover, album_obj)

        i = 0
        a = -1
        previous_column = 0

        grid_songs = box.get_object('grid_songs')
        grid_songs.set_size_request(-1, 200)
        grid_songs.set_column_spacing(5)

        try:
            kids = grid_songs.get_children()
            for kid in kids:
                grid_songs.remove(kid)
        except IndexError:
            pass

        for song in album_obj.tracks:
            i += 1
            a += 1

            def queue(wdg, song):
                self.queue(song)

            def play(wdg, song):
                self.play(song)

            song_wdg = Box(spacing=0)
            song_btr = Button()
            song_btr.connect('clicked', play, song)
            song_btr.set_relief(ReliefStyle.NONE)
            song_btr_content = Box(spacing=0)
            song_btr.add(song_btr_content)

            song_tr = Label()
            song_tr.set_markup('<span foreground="grey">' + str(song.track)
                               + '</span>')
            song_tr.set_width_chars(3)
            song_btr_content.pack_start(song_tr, False, True, 0)
            song_ti = Label()
            song_ti.set_markup('<b>' + self.functions.view_encode(song.title, 22)
                               + '</b>')
            song_ti.set_alignment(0.0, 0.5)
            song_ti.set_size_request(190, -1)
            song_btr_content.pack_start(song_ti, False, False, 0)

            length = self.functions.human_length(song.length)
            song_le = Label()
            song_le.set_markup('<span foreground="grey">' + length
                               + '</span>')
            song_le.set_width_chars(5)
            song_btr_content.pack_start(song_le, False, True, 0)

            song_wdg.pack_start(song_btr, False, False, 0)

            song_add = Button.new_from_icon_name('list-add-symbolic', 0)
            song_add.set_property('relief', 2)
            song_add.set_size_request(14, 14)
            song_add.connect('clicked', queue, song)
            song_wdg.pack_start(song_add, False, False, 0)

            if i <= len(album_obj.tracks)/2:
                column = 0
                previous_column = 0
                row = a
            else:
                if previous_column == 0:
                    a = 0
                column = 1
                previous_column = 1
                row = a

            grid_songs.attach(song_wdg, column, row, 1, 1)
        popup.show_all()
Esempio n. 2
0
class CardsGenerator(Window):
    def __init__(self, parent: Window, app: Application,
                 col_filename: Filename, vid_filename: Filename,
                 sub_filename: Filename, opt_sub_filename: OptFilename,
                 deck_name: str):

        super().__init__(title='Asts - Anki Card Generator',
                         application=app,
                         transient_for=parent)

        self.set_default_size(width=1000, height=700)
        self.set_keep_above(True)
        self.set_modal(True)
        self.set_resizable(False)

        setBgColor(widget=self, alpha=0.93)

        self._main_box: Box = Box()
        self._main_box.set_orientation(Orientation.VERTICAL)
        setMargin(self._main_box, 10)

        self.add(self._main_box)

        self._subtitles_grid: Grid = Grid()
        setMargin(self._subtitles_grid, 5)

        # box.pack_(expand, fill, padding)
        self._main_box.pack_start(self._subtitles_grid, False, True, 0)

        self._collection_filename: Filename = col_filename
        self._video_filename: Filename = vid_filename
        self._subtitles_filename: Filename = sub_filename
        self._opt_subtitles_filename: OptFilename = opt_sub_filename
        self._deck_name: str = deck_name

        self._any_media_toggled: bool = False
        self._dict_any_media: Dict[str, bool]

        self._dict_any_change_front: Dict[str, bytes]
        self._dict_any_change_back: Dict[str, bytes]

        self._textview_front: TextView
        self._textview_back: TextView

        self._textbuffer_front: TextBuffer
        self._textbuffer_back: TextBuffer

        self._subtitles_liststore: ListStore
        self._subtitles_liststore_back: ListStore

        self._subtitles_treeview: TreeView

        self._selected_row: TreeSelection

        self._progress_bar: ProgressBar

        self._cancel_btn: Button
        self._generate_btn: Button

        self._cur_progress: int
        self._max_tasks: int

        self._cancel_task: bool

        self._list_of_sentences: ListSentences
        self._list_info_medias: List[List[Info]]

        self._color_tag_names: List[str]
        # TheadingHandler will utilize these
        # updating the status for each task tasks
        # also the sensitive and progress of the progress bar
        # depends on these.
        self._futures_list: List[Future]

    def showAll(self) -> None:
        """
        Draws the cards generator window and it's respective widgets.

        :return:
        """

        # subtitles tree view
        self._setSubtitleTreeView()

        # indice and dialogue cells
        self._setDialogCells()

        # start and end timer cells
        self._setTimerCells()

        # video, audio and image cells
        self._setMediasCells()

        # fills both tree view with the subtitles
        self._populateListStore()

        # setting the model after and initializing _selected_row and _dict_any_media
        # after the liststore being complete initialized
        self._subtitles_treeview.set_model(self._subtitles_liststore)

        self._selected_row = self._subtitles_treeview.get_selection()
        self._selected_row.connect('changed', self._itemSelected)

        self._dict_any_media = {
            str(key): False
            for key in enumerate(self._subtitles_liststore)
        }

        # search entry
        self._setSearchEntry()

        # sets up the sentence editing related (e.g toolbar, tagging, etc)
        self._setSentenceRelated()

        # all color tags are named as it's respective values
        self._color_tag_names = [
            '#9999c1c1f1f1',
            '#6262a0a0eaea',
            '#35358484e4e4',
            '#1c1c7171d8d8',
            '#1a1a5f5fb4b4',
            '#8f8ff0f0a4a4',
            '#5757e3e38989',
            '#3333d1d17a7a',
            '#2e2ec2c27e7e',
            '#2626a2a26969',
            '#f9f9f0f06b6b',
            '#f8f8e4e45c5c',
            '#f6f6d3d32d2d',
            '#f5f5c2c21111',
            '#e5e5a5a50a0a',
            '#ffffbebe6f6f',
            '#ffffa3a34848',
            '#ffff78780000',
            '#e6e661610000',
            '#c6c646460000',
            '#f6f661615151',
            '#eded33333b3b',
            '#e0e01b1b2424',
            '#c0c01c1c2828',
            '#a5a51d1d2d2d',
            '#dcdc8a8adddd',
            '#c0c06161cbcb',
            '#91914141acac',
            '#81813d3d9c9c',
            '#616135358383',
            '#cdcdabab8f8f',
            '#b5b583835a5a',
            '#98986a6a4444',
            '#86865e5e3c3c',
            '#636345452c2c',
            '#ffffffffffff',
            '#f6f6f5f5f4f4',
            '#dededddddada',
            '#c0c0bfbfbcbc',
            '#9a9a99999696',
            '#777776767b7b',
            '#5e5e5c5c6464',
            '#3d3d38384646',
            '#24241f1f3131',
            '#000000000000',
        ]

        # sets up dictionary used to track the tags used
        self._initDictionariesTag()

        # sets up the buttons to select all sentences
        self._setSelectAll()

        # sets up the progress bar
        self._setProgressBar()

        # cancel and generate buttonsj
        self._resetFuturesLists()
        self._setButtons()

        self.show_all()

    def _resetFuturesLists(self) -> None:
        """
        Assign a empty list of both lists of futures (futures_setences and futures_medias).

        :return:
        """

        self._futures_list = []

    def _setSearchEntry(self) -> None:
        """
        Connect the changed event for the search_entry object.

        :return:
        """

        search_entry: SearchEntry = SearchEntry()
        search_entry.set_halign(Align.END)

        setMargin(search_entry, 0, 5, 0, 5)

        self._subtitles_grid.attach(search_entry, 0, 0, 1, 1)

        search_entry.connect('changed', self.searchIt)

    def searchIt(self, search_entry: SearchEntry) -> None:
        """
        Searchs over the _subtitles_liststore.

        :return:
        """

        term_searched: str = search_entry.get_text()

        for i, term in enumerate(self._subtitles_liststore):
            if term_searched and term_searched in term[1].lower():
                self._subtitles_treeview.set_cursor(i)
                break

    def _setSelectAll(self) -> None:
        """
        Sets up widgets to select all sentences.

        :return:
        """

        grid: Grid = Grid()
        grid.set_halign(Align.END)
        self._subtitles_grid.attach(grid, 0, 2, 1, 1)

        lbl: Label = Label(label='Select all')
        setMargin(lbl, 5)

        grid.attach(lbl, 0, 0, 1, 1)

        all_vid_toggle: CheckButton = CheckButton()
        all_vid_toggle.set_halign(Align.CENTER)
        all_vid_toggle.connect('toggled', self._onAllVideosToggled)

        setMargin(all_vid_toggle, 5)

        grid.attach(all_vid_toggle, 1, 0, 1, 1)

        lbl2: Label = Label(label='Videos')

        setMargin(lbl2, 5)

        grid.attach(lbl2, 1, 1, 1, 1)

        all_audio_toggle: CheckButton = CheckButton()
        all_audio_toggle.set_halign(Align.CENTER)
        all_audio_toggle.connect('toggled', self._onAllAudiosToggled,
                                 all_vid_toggle)

        setMargin(all_audio_toggle, 5)

        grid.attach(all_audio_toggle, 2, 0, 1, 1)

        lbl3: Label = Label(label='Audios')

        setMargin(lbl3, 5)

        grid.attach(lbl3, 2, 1, 1, 1)

        all_img_toggle: CheckButton = CheckButton()
        all_img_toggle.set_halign(Align.CENTER)
        all_img_toggle.connect('toggled', self._onAllImagesToggled)

        setMargin(all_img_toggle, 5)

        grid.attach(all_img_toggle, 3, 0, 1, 1)

        lbl4: Label = Label(label='Snapshot')

        setMargin(lbl4, 5)

        grid.attach(lbl4, 3, 1, 1, 1)

    def _onAllVideosToggled(self, _) -> None:
        """
        Handle the toggled event for the ToggleButton object.

        :param widget: ToggleButton object.
        :return:
        """

        for i in range(len(self._subtitles_liststore)):
            if self._subtitles_liststore[i][5]:

                self._subtitles_liststore[i][
                    5] = not self._subtitles_liststore[i][5]
                self._subtitles_liststore[i][
                    4] = not self._subtitles_liststore[i][4]
                self._dict_any_media[str(i)] = self._subtitles_liststore[i][4]

            elif self._subtitles_liststore[i][6]:

                self._subtitles_liststore[i][
                    6] = not self._subtitles_liststore[i][6]
                self._subtitles_liststore[i][
                    4] = not self._subtitles_liststore[i][4]
                self._dict_any_media[str(i)] = self._subtitles_liststore[i][4]

            else:
                self._subtitles_liststore[i][
                    4] = not self._subtitles_liststore[i][4]
                self._dict_any_media[str(i)] = self._subtitles_liststore[i][4]

        if True in self._dict_any_media.values():
            self._any_media_toggled = True
        else:
            self._any_media_toggled = False

    def _onAllAudiosToggled(self, _) -> None:
        """
        Handle the toggled event for the ToggleButton object.

        :param widget: ToggleButton object.
        :return:
        """

        for i in range(len(self._subtitles_liststore)):
            if self._subtitles_liststore[i][4]:

                self._subtitles_liststore[i][
                    4] = not self._subtitles_liststore[i][4]
                self._subtitles_liststore[i][
                    5] = not self._subtitles_liststore[i][5]
                self._dict_any_media[str(i)] = self._subtitles_liststore[i][5]

            elif self._subtitles_liststore[i][5] and self._subtitles_liststore[
                    i][6]:

                self._subtitles_liststore[i][
                    5] = not self._subtitles_liststore[i][5]
                self._dict_any_media[str(i)] = self._subtitles_liststore[i][6]

            else:
                self._subtitles_liststore[i][
                    5] = not self._subtitles_liststore[i][5]
                self._dict_any_media[str(i)] = self._subtitles_liststore[i][5]

        if True in self._dict_any_media.values():
            self._any_media_toggled = True
        else:
            self._any_media_toggled = False

    def _onAllImagesToggled(self, _) -> None:
        """
        Handle the toggled event for the ToggleButton object.

        :param widget: ToggleButton object.
        :return:
        """

        for i in range(len(self._subtitles_liststore)):
            if self._subtitles_liststore[i][4]:

                self._subtitles_liststore[i][
                    4] = not self._subtitles_liststore[i][4]
                self._subtitles_liststore[i][
                    6] = not self._subtitles_liststore[i][6]
                self._dict_any_media[str(i)] = self._subtitles_liststore[i][6]

            else:
                self._subtitles_liststore[i][
                    6] = not self._subtitles_liststore[i][6]
                self._dict_any_media[str(i)] = self._subtitles_liststore[i][6]

        if True in self._dict_any_media.values():
            self._any_media_toggled = True
        else:
            self._any_media_toggled = False

    def _initDictionariesTag(self) -> None:
        """
        Init the default values for the used tags.

        :return:
        """

        # dictionaries to track the tags
        self._dict_any_change_front = ({
            str(key): serializeIt(text_buffer=self._textbuffer_front,
                                  tmp_string=value[1])
            for key, value in enumerate(self._subtitles_liststore)
        })

        self._dict_any_change_back = ({
            str(key): serializeIt(text_buffer=self._textbuffer_back,
                                  tmp_string=value[1])
            for key, value in enumerate(self._subtitles_liststore_back)
        })

    def _populateListStore(self) -> None:
        """
        Fills both list store (front and back) with subtitles.

        :return:
        """

        self._subtitles_liststore = ListStore(
            int,  # indice
            str,  # dialogue
            str,  # start timer
            str,  # end timer
            bool,  # whether video is selected
            bool,  # whether audio is selected
            bool  # whether image is selected
        )

        # only the first two values are important here
        self._subtitles_liststore_back = ListStore(int, str, str, str, bool,
                                                   bool, bool)

        dialogues_list: List[List[Info]] = extractAllDialogues(
            self._subtitles_filename)

        for dialogue in dialogues_list:
            self._subtitles_liststore.append(dialogue)

        if self._opt_subtitles_filename:
            opt_dialogues_list: List[List[Info]] = extractAllDialogues(
                self._opt_subtitles_filename)

            # the subtitles and their respective translations
            # may or may not be of same lenght
            # in that case fill the list with dummy values
            for i in range(len(dialogues_list)):
                try:
                    self._subtitles_liststore_back.append(
                        opt_dialogues_list[i])
                except IndexError:
                    self._subtitles_liststore_back.append(
                        (i, '', '', '', False, False, False))
        else:
            # in case no subtitles was selected for the back list store
            # fill it with dummy values
            for i in range(len(dialogues_list)):
                self._subtitles_liststore_back.append(
                    (i, '', '', '', False, False, False))

    def _setTimerCells(self) -> None:
        """
        Arrange the start and end timer cells.

        :return:
        """
        # Making some cells editable 'Start' and 'End' respectivily
        editable_start_field: CellRendererText = CellRendererText()
        editable_end_field: CellRendererText = CellRendererText()

        editable_start_field.set_property('editable', True)
        editable_end_field.set_property('editable', True)

        self._subtitles_treeview.append_column(
            TreeViewColumn(title='Start',
                           cell_renderer=editable_start_field,
                           text=2))

        self._subtitles_treeview.append_column(
            TreeViewColumn(title='End',
                           cell_renderer=editable_end_field,
                           text=3))

        editable_start_field.connect('edited', self._startFieldEdited)
        editable_end_field.connect('edited', self._endFieldEdited)

    def _startFieldEdited(self, _, path: TreePath, text: str) -> None:
        """
        Handle the edited event for the start timer field cell.

        :widget: CellRendererText object.
        :path: TreePath object.
        :text: A string to be assigned to subtitles_liststore.
        :return:
        """

        from re import compile, Pattern

        regex_timer: Pattern[str] = compile(
            r'([0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9])')
        result = regex_timer.findall(text)

        if result:
            self._subtitles_liststore[path][2] = result[0]

    def _endFieldEdited(self, _, path: TreePath, text: str) -> None:
        """
        Handle the edited event for the end timer field cell.

        :widget: CellRendererText object.
        :path: TreePath object.
        :text: A string to be assigned to subtitles_liststore.
        :return:
        """

        from re import compile, Pattern

        regex_timer: Pattern[str] = compile(
            r'([0-9]?[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9])')
        result: List[str] = regex_timer.findall(text)

        if result:
            self._subtitles_liststore[path][3] = result[0]

    def _setDialogCells(self) -> None:
        """
        Arrange the dialogue and indice cell at the treeview.

        :return:
        """

        for i, title in enumerate(['Indice', 'Dialog']):
            renderer: CellRendererText = CellRendererText()
            path_column: TreeViewColumn = TreeViewColumn(
                title=title, cell_renderer=renderer, text=i)

            if title == 'Dialog':
                path_column.set_sizing(TreeViewColumnSizing.FIXED)
                path_column.set_fixed_width(520)
                path_column.set_min_width(520)
            self._subtitles_treeview.append_column(path_column)

    def _setMediasCells(self) -> None:
        """
        Arrange the video, audio and snapshot cells.

        :return:
        """

        # cell video, audio and snapshot to toggle
        renderer_video_toggle: CellRendererToggle = CellRendererToggle()
        column_toggle = TreeViewColumn(title='Video',
                                       cell_renderer=renderer_video_toggle,
                                       active=4)
        self._subtitles_treeview.append_column(column_toggle)
        renderer_video_toggle.connect("toggled", self._onCellVideoToggled)

        renderer_audio_toggle: CellRendererToggle = CellRendererToggle()
        column_toggle = TreeViewColumn(title='Audio',
                                       cell_renderer=renderer_audio_toggle,
                                       active=5)
        self._subtitles_treeview.append_column(column_toggle)
        renderer_audio_toggle.connect("toggled", self._onCellAudioToggled)

        renderer_snapshot_toggle: CellRendererToggle = CellRendererToggle()
        column_toggle = TreeViewColumn(title='Snapshot',
                                       cell_renderer=renderer_snapshot_toggle,
                                       active=6)
        self._subtitles_treeview.append_column(column_toggle)
        renderer_snapshot_toggle.connect("toggled", self._onCellImageToggled)

    def _onCellVideoToggled(self, _, path) -> None:
        """
        Handles the toggled event for the CellRendererToggle object.
        
        :param widget: CellRendererToggle object.
        :path path: TreePath object.
        :return:
        """

        if self._subtitles_liststore[path][5]:

            self._subtitles_liststore[path][
                5] = not self._subtitles_liststore[path][5]
            self._subtitles_liststore[path][
                4] = not self._subtitles_liststore[path][4]
            self._dict_any_media[path] = self._subtitles_liststore[path][4]

        elif self._subtitles_liststore[path][6]:

            self._subtitles_liststore[path][
                6] = not self._subtitles_liststore[path][6]
            self._subtitles_liststore[path][
                4] = not self._subtitles_liststore[path][4]
            self._dict_any_media[path] = self._subtitles_liststore[path][4]

        else:
            self._subtitles_liststore[path][
                4] = not self._subtitles_liststore[path][4]
            self._dict_any_media[path] = self._subtitles_liststore[path][4]

        if True in self._dict_any_media.values():
            self._any_media_toggled = True
        else:
            self._any_media_toggled = False

    def _onCellAudioToggled(self, _, path: str) -> None:
        """
        Handles the toggled event for the CellRendererToggle object.
        
        :param widget: CellRendererToggle object.
        :path path: TreePath object.
        :return:
        """

        if self._subtitles_liststore[path][4]:

            self._subtitles_liststore[path][
                4] = not self._subtitles_liststore[path][4]
            self._subtitles_liststore[path][
                5] = not self._subtitles_liststore[path][5]
            self._dict_any_media[path] = self._subtitles_liststore[path][5]

        elif self._subtitles_liststore[path][5] and self._subtitles_liststore[
                path][6]:

            self._subtitles_liststore[path][
                5] = not self._subtitles_liststore[path][5]
            self._dict_any_media[path] = self._subtitles_liststore[path][6]

        else:
            self._subtitles_liststore[path][
                5] = not self._subtitles_liststore[path][5]
            self._dict_any_media[path] = self._subtitles_liststore[path][5]

        if True in self._dict_any_media.values():
            self._any_media_toggled = True
        else:
            self._any_media_toggled = False

    def _onCellImageToggled(self, _, path: str) -> None:
        """
        Handles the toggled event for the CellRendererToggle object.
        
        :param widget: CellRendererToggle object.
        :path path: TreePath object.
        :return:
        """

        if self._subtitles_liststore[path][4]:

            self._subtitles_liststore[path][
                4] = not self._subtitles_liststore[path][4]
            self._subtitles_liststore[path][
                6] = not self._subtitles_liststore[path][6]
            self._dict_any_media[path] = self._subtitles_liststore[path][6]

        elif self._subtitles_liststore[path][6] and self._subtitles_liststore[
                path][5]:

            self._subtitles_liststore[path][
                6] = not self._subtitles_liststore[path][6]
            self._dict_any_media[path] = self._subtitles_liststore[path][5]

        else:
            self._subtitles_liststore[path][
                6] = not self._subtitles_liststore[path][6]
            self._dict_any_media[path] = self._subtitles_liststore[path][6]

        if True in self._dict_any_media.values():
            self._any_media_toggled = True
        else:
            self._any_media_toggled = False

    def _setSubtitleTreeView(self) -> None:
        """
        Sets the scrolled window and a tree view for subtitles info.

        :return:
        """

        self._subtitles_treeview = TreeView()
        self._subtitles_treeview.set_grid_lines(TreeViewGridLines.BOTH)

        scrl_wnd: ScrolledWindow = ScrolledWindow()
        scrl_wnd.set_hexpand(True)
        scrl_wnd.set_vexpand(True)

        scrl_wnd.add(self._subtitles_treeview)

        self._subtitles_grid.attach(scrl_wnd, 0, 1, 1, 1)

    def _itemSelected(self, _) -> None:
        """
        Keeps tracks of selections change at the treeview object.

        :return:
        """

        path: str = self._selected_row.get_selected_rows()[1][0].to_string()

        deserializeIt(self._textbuffer_front,
                      self._dict_any_change_front[path])
        deserializeIt(self._textbuffer_back, self._dict_any_change_back[path])

        self._textbuffer_front.connect('changed', self._editingCard)
        self._textbuffer_back.connect('changed', self._editingCardBack)

    def _editingCard(self, textbuffer_front: TextBuffer) -> None:
        """
        Keeps track of changes at the text_buffer_front.

        :param text_buffer_front: TextBuffer object.
        :return:
        """

        path: TreePath = self._selected_row.get_selected_rows()[1][0]
        start_iter_front: TextIter = textbuffer_front.get_start_iter()
        end_iter_front: TextIter = textbuffer_front.get_end_iter()
        self._subtitles_liststore[path][1] = textbuffer_front.get_text(
            start_iter_front, end_iter_front, True)

        self._dict_any_change_front[path.to_string()] = serializeIt(
            text_buffer=textbuffer_front)

    def _editingCardBack(self, textbuffer_back: TextBuffer) -> None:
        """
        Keeps track of changes at the text_buffer_back.

        :param text_buffer_back: TextBuffer object.
        :return:
        """

        path: TreePath = self._selected_row.get_selected_rows()[1][0]
        start_iter_back: TextIter = textbuffer_back.get_start_iter()
        end_iter_back: TextIter = textbuffer_back.get_end_iter()
        self._subtitles_liststore_back[path][1] = textbuffer_back.get_text(
            start_iter_back, end_iter_back, True)

        self._dict_any_change_back[path.to_string()] = serializeIt(
            text_buffer=textbuffer_back)

    def _setSentenceRelated(self) -> None:
        """
        Sets up the sentence editing widgets related.
        Also initialize both text buffers.

        :return:
        """

        box: Box = Box()

        self._main_box.pack_start(box, False, True, 0)

        box.set_orientation(Orientation.VERTICAL)

        setMargin(box, 5)

        toolbar: Toolbar = Toolbar()

        box.pack_start(toolbar, False, True, 0)

        toolbar.set_halign(Align.END)
        setMargin(toolbar, 5)

        lbl: Label = Label()
        lbl.set_markup('<i><b>Front</b></i>')

        box.pack_start(lbl, False, True, 0)

        lbl.set_halign(Align.START)
        setMargin(lbl, 5)

        scrl_wnd: ScrolledWindow = ScrolledWindow()
        scrl_wnd.set_hexpand(True)
        scrl_wnd.set_vexpand(True)

        textview: TextView = TextView()
        scrl_wnd.add(textview)

        box.pack_start(scrl_wnd, False, True, 0)

        self._textbuffer_front = textview.get_buffer()

        lbl2: Label = Label()
        lbl2.set_halign(Align.START)
        lbl2.set_markup('<i><b>Back</b></i>')

        box.pack_start(lbl2, False, True, 0)
        setMargin(lbl2, 5)

        scrl_wnd2: ScrolledWindow = ScrolledWindow()
        scrl_wnd2.set_hexpand(True)
        scrl_wnd2.set_vexpand(True)

        textview2: TextView = TextView()
        scrl_wnd2.add(textview2)

        box.pack_end(scrl_wnd2, False, True, 0)

        self._textbuffer_back = textview2.get_buffer()

        # this depends on the text buffer to be initialized
        self._setToolbarColorButton(toolbar)

        toolbar.insert(SeparatorToolItem(), 3)

        self._setToolbarUnderlineButton(toolbar)
        self._setToolbarBoldButton(toolbar)
        self._setToolbarItalicButton(toolbar)

        toolbar.insert(SeparatorToolItem(), 7)

        self._setToolbarTagRemoverButton(toolbar)

    def _setToolbarColorButton(self, toolbar: Toolbar) -> None:
        """
        Sets up the color button from the toolbar.

        :param toolbar: Toolbar object
        :return:
        """

        set_color_button: ToolButton = ToolButton()
        set_color_button.set_icon_name('gtk-select-color')
        toolbar.insert(set_color_button, 1)

        tool_item_color_button: ToolItem = ToolItem()
        color_button = ColorButton()

        tool_item_color_button.add(color_button)
        toolbar.insert(tool_item_color_button, 2)

        set_color_button.connect('clicked', self._onToolbarColorBtnClicked,
                                 color_button)

    def _setToolbarUnderlineButton(self, toolbar: Toolbar) -> None:
        """
        Sets up the underline button from the toolbar.

        :param toolbar: Toolbar object
        :return:
        """

        tag_underline_front: TextTag = self._textbuffer_front.create_tag(
            'underline', underline=Underline.SINGLE)
        tag_underline_back: TextTag = self._textbuffer_back.create_tag(
            'underline', underline=Underline.SINGLE)
        button_underline: ToolButton = ToolButton()

        button_underline.set_icon_name('format-text-underline-symbolic')
        toolbar.insert(button_underline, 4)

        button_underline.connect('clicked', self._onToolbarTagBtnClicked,
                                 tag_underline_front, tag_underline_back)

    def _setToolbarBoldButton(self, toolbar: Toolbar) -> None:
        """
        Sets up the bold button from the toolbar.

        :param toolbar: Toolbar object
        :return:
        """

        tag_bold_front: TextTag = self._textbuffer_front.create_tag(
            'bold', weight=Weight.BOLD)
        tag_bold_back: TextTag = self._textbuffer_back.create_tag(
            'bold', weight=Weight.BOLD)
        button_bold: ToolButton = ToolButton()

        button_bold.set_icon_name('format-text-bold-symbolic')
        toolbar.insert(button_bold, 5)

        button_bold.connect('clicked', self._onToolbarTagBtnClicked,
                            tag_bold_front, tag_bold_back)

    def _setToolbarItalicButton(self, toolbar: Toolbar) -> None:
        """
        Sets up the italic button from the toolbar.

        :param toolbar: Toolbar object
        :return:
        """

        tag_italic_front: TextTag = self._textbuffer_front.create_tag(
            'italic', style=Style.ITALIC)
        tag_italic_back: TextTag = self._textbuffer_back.create_tag(
            'italic', style=Style.ITALIC)

        button_italic: ToolButton = ToolButton()
        button_italic.set_icon_name('format-text-italic-symbolic')
        toolbar.insert(button_italic, 6)

        button_italic.connect('clicked', self._onToolbarTagBtnClicked,
                              tag_italic_front, tag_italic_back)

    def _setToolbarTagRemoverButton(self, toolbar: Toolbar) -> None:
        """
        Sets up the tag remover button from the toolbar.

        :param toolbar: Toolbar object.
        :return:
        """

        button_remove_all_tags: ToolButton = ToolButton()

        button_remove_all_tags.set_icon_name('edit-clear-symbolic')
        toolbar.insert(button_remove_all_tags, 8)

        button_remove_all_tags.connect(
            'clicked', lambda _: self._removeAllTagsFromSelection())

    def _getBounds(self) -> Tuple[TextMark, TextMark, Optional[str]]:
        """
        Returns the selection of the text in the text buffer.

        :return: A tuple with the textiter of the selection and the path string.
        """

        path: Optional[str]

        # if no row is selected
        # a IndexError will be raised
        try:
            path = self._selected_row.get_selected_rows()[1][0].to_string()
        except IndexError:
            path = None

        bounds_front: TextMark = self._textbuffer_front.get_selection_bounds()
        bounds_back: TextMark = self._textbuffer_back.get_selection_bounds()

        return (bounds_front, bounds_back, path)

    def _onToolbarColorBtnClicked(self, _, color_button: ColorButton) -> None:
        """
        Handles the clicked event for the tool_item_color_button.

        :param set_color_button: ToolButton object.
        :param color_button: ColorButton object.
        :return:
        """

        start: TextIter
        end: TextIter
        bounds_front: TextMark
        bounds_back: TextMark
        path: Optional[str]

        color: str = color_button.get_color().to_string()

        tag_table_front: TextTagTable = self._textbuffer_front.get_tag_table()
        tag_table_back: TextTagTable = self._textbuffer_back.get_tag_table()

        (bounds_front, bounds_back, path) = self._getBounds()

        # no selected row so there's nothing to do
        if not path:
            return

        ##### FRONT
        if bounds_front:
            (start, end) = bounds_front

            # only the first color applied to the selection
            # will be present at the final card
            # so remove all color previously applied to the current selected text.
            self._removeAllTagsFromSelection(color_tags=True)

            if not tag_table_front.lookup(color):
                tag_front: TextTag = self._textbuffer_front.create_tag(
                    color, foreground=color)
                self._textbuffer_front.apply_tag(tag_front, start, end)
            else:
                self._textbuffer_front.apply_tag_by_name(color, start, end)

            self._dict_any_change_front[path] = serializeIt(
                text_buffer=self._textbuffer_front)

        ###### BACK
        if bounds_back:
            (start, end) = bounds_back

            # only the first color applied to the selected text
            # will be present at the final card
            # so remove all color previously applied to the current selected text.
            self._removeAllTagsFromSelection(color_tags=True)

            if not tag_table_back.lookup(color):
                tag_back = self._textbuffer_back.create_tag(color,
                                                            foreground=color)
                self._textbuffer_back.apply_tag(tag_back, start, end)
            else:
                self._textbuffer_back.apply_tag_by_name(color, start, end)

            self._dict_any_change_back[path] = serializeIt(
                text_buffer=self._textbuffer_back)

    def _onToolbarTagBtnClicked(self, _, tag_front: TextTag,
                                tag_back: TextTag) -> None:
        """
        Handles the clicked event for the tool button. 

        :param widget: ToolButton object.
        :param tag_front: TextTag object.
        :param tag_back: TextTag object.
        :return:
        """

        start: TextIter
        end: TextIter
        bounds_front: TextMark
        bounds_back: TextMark
        path: Optional[str]

        (bounds_front, bounds_back, path) = self._getBounds()

        # no selected row so there's nothing to do
        if not path:
            return

        ##### FRONT
        if bounds_front:
            (start, end) = bounds_front

            self._textbuffer_front.apply_tag(tag_front, start, end)

            self._dict_any_change_front[path] = serializeIt(
                text_buffer=self._textbuffer_front)

        ###### BACK
        if bounds_back:
            (start, end) = bounds_back
            self._textbuffer_back.apply_tag(tag_back, start, end)

            self._dict_any_change_back[path] = serializeIt(
                text_buffer=self._textbuffer_back)

    def _removeAllTagsFromSelection(self, color_tags: bool = False) -> None:
        """
        Remove all tags from the current selected text.

        :param color_tags: If true only removes color tags.
        :return:
        """

        start: TextIter
        end: TextIter
        bounds_front: TextMark
        bounds_back: TextMark
        path: Optional[str]

        tag_table_front: TextTagTable = self._textbuffer_front.get_tag_table()
        tag_table_back: TextTagTable = self._textbuffer_back.get_tag_table()

        (bounds_front, bounds_back, path) = self._getBounds()

        # no selected row so there's nothing to do
        if not path:
            return

        ### FRONT
        if bounds_front:
            (start, end) = bounds_front

            if color_tags:
                for c in self._color_tag_names:
                    if tag_table_front.lookup(c):
                        self._textbuffer_front.remove_tag_by_name(
                            c, start, end)
            else:
                self._textbuffer_front.remove_all_tags(start, end)

            self._dict_any_change_front[path] = serializeIt(
                text_buffer=self._textbuffer_front)

        ### BACK
        if bounds_back:
            (start, end) = bounds_back

            if color_tags:
                for c in self._color_tag_names:
                    if tag_table_back.lookup(c):
                        self._textbuffer_back.remove_tag_by_name(c, start, end)
            else:
                self._textbuffer_back.remove_all_tags(start, end)

            self._dict_any_change_back[path] = serializeIt(
                text_buffer=self._textbuffer_back)

    def _setProgressBar(self) -> None:
        """
        Sets up the progress bar.

        :return:
        """

        self._cur_progress = 0

        self._progress_bar = ProgressBar()

        setMargin(self._progress_bar, 5)

        self._main_box.pack_start(self._progress_bar, False, True, 0)

    def _setButtons(self) -> None:
        """
        Sets up the cancel and generate buttons.

        :return:
        """

        box: Box = Box()

        self._main_box.pack_end(box, False, True, 0)

        box.set_halign(Align.CENTER)
        box.set_orientation(Orientation.HORIZONTAL)

        setMargin(box, 5)

        self._cancel_btn = Button(label='Cancel')

        box.pack_start(self._cancel_btn, False, True, 0)

        setMargin(self._cancel_btn, 5, 5, 100, 5)
        self._cancel_btn.connect('clicked', self._onCancelBtnClicked)

        self._generate_btn = Button(label='Generate')

        box.pack_end(self._generate_btn, False, True, 0)

        setMargin(self._generate_btn, 100, 5, 5, 5)
        self._generate_btn.connect('clicked', self._onGenerateBtnClicked)

        timeout_add(300, self._setSensitiveGenerateBtn)

    def _setSensitiveGenerateBtn(self) -> bool:
        """
        Set the senstive for the generate_btn.

        :return: A boolean to signal whether idle_add should remove it from list event.
        """

        if self._cur_progress or not self._allFuturesDone():
            self._generate_btn.set_sensitive(False)
        elif not self._any_media_toggled:
            self._generate_btn.set_sensitive(False)
        else:
            self._generate_btn.set_sensitive(True)

        return True

    def _allFuturesDone(self) -> bool:
        """
        Check for the status of futures.

        :return: Return true if all futures are done.
        """

        for f in self._futures_list:
            if not f.done(): return False

        return True

    def _updateProgress(self) -> bool:
        """
        Keep track of the objects yet to be completed.
        Updates the progress bar.
        
        :param future: Parameter passed by add_done_callback.
        :return: a boolean to signal whether idle_add should remove it from list event.
        """

        if not self.getCancelTaskStatus():
            self._cur_progress += 1
            self._progress_bar.set_fraction(self._cur_progress /
                                            self._max_tasks)
            self._progress_bar.set_text(None)
            self._progress_bar.set_show_text(True)

            if self._cur_progress == self._max_tasks:
                self._cur_progress = 0
                self._progress_bar.set_text('Done!')
                self._progress_bar.set_show_text(True)

        return False

    def resetProgressbar(self) -> None:
        """
        Resets the progress bar back to zero.

        :return:
        """

        self._cur_progress = 0
        self._progress_bar.set_fraction(self._cur_progress)
        self._progress_bar.set_show_text(False)

    def idleaddUpdateProgress(self, _) -> None:
        """
        Call idle_add to call updateProgress.
        
        :param future: Optional future object.
        :return:
        """

        idle_add(self._updateProgress)

    def getCancelTaskStatus(self) -> bool:
        """
        Get the status for the cancel_task.

        :return: Return true if the task should be cancelled.
        """

        return self._cancel_task

    def setCancelTaskStatus(self, status: bool) -> None:
        """
        Set the status for the cancel_task.

        :return:
        """

        self._cancel_task = status

    def _idleaddUpdateProgress(self, _) -> None:
        """
        Call idle_add to call updateProgress.
        
        :param future: Optional future object.
        :return:
        """

        idle_add(self._updateProgress)

    def _setSensitiveCancelBtn(self) -> bool:
        """
        Set the sensitive for snd_cancel_button.

        :return:
        """

        if self._allFuturesDone():
            self._progress_bar.set_text('Canceled!')

            self._cancel_btn.set_sensitive(True)

            return False
        else:
            self._progress_bar.set_text('Cancelling please wait...')
            self._cancel_btn.set_sensitive(False)

        return True

    def _onCancelBtnClicked(self, _) -> None:
        """
        Handle the clicked event for the second_cancel_button button.

        :param widget: Button object.
        :return:
        """

        if not self._cur_progress:
            self._generate_btn.set_sensitive(True)
            self.close()
        else:
            self.setCancelTaskStatus(True)

            self._cur_progress = 0

            self._progress_bar.set_fraction(self._cur_progress)
            self._progress_bar.set_show_text(True)

            timeout_add(300, self._setSensitiveCancelBtn)

        self._cur_progress = 0
        self._progress_bar.set_fraction(self._cur_progress)

    def _onGenerateBtnClicked(self, _) -> None:
        """
        Handle the click event for the generate_btn.

        :return:
        """

        from asts.Threading import ThreadingHandler

        self._listMediasSentences()

        ThreadingHandler(self)

    def _listMediasSentences(self) -> None:
        """
        Create two lists and fill them with filenames and sentences.

        :return:
        """

        from uuid import uuid1

        from asts.Utils import PangoToHtml

        # case other tasks already had been scheduled
        self._resetFuturesLists()

        self._list_of_sentences = []
        self._list_info_medias = []

        p: PangoToHtml = PangoToHtml()

        for i in range(len(self._subtitles_liststore)):
            if self._subtitles_liststore[i][4] or self._subtitles_liststore[i][
                    5] or self._subtitles_liststore[i][6]:
                # a unique id for each media, some images will conflict if it has the same name as a image
                # on anki media collection
                uuid_media = uuid1().int

                text_front: str = p.feed(self._dict_any_change_front[str(i)])
                text_back: str = p.feed(self._dict_any_change_back[str(i)])

                self._list_info_medias.append(
                    [str(uuid_media)] + (self._subtitles_liststore[i][1:]))

                if self._subtitles_liststore[i][
                        4] and not self._subtitles_liststore[i][6]:
                    self._list_of_sentences.append(
                        (text_front, text_back, f'{uuid_media}.mp4', None,
                         None))
                elif self._subtitles_liststore[i][
                        5] and self._subtitles_liststore[i][6]:
                    self._list_of_sentences.append(
                        (text_front, text_back, None, f'{uuid_media}.mp3',
                         f'{uuid_media}.bmp'))
                elif self._subtitles_liststore[i][
                        5] and not self._subtitles_liststore[i][6]:
                    self._list_of_sentences.append(
                        (text_front, text_back, None, f'{uuid_media}.mp3',
                         None))
                else:
                    self._list_of_sentences.append(
                        (text_front, text_back, None, None,
                         f'{uuid_media}.bmp'))

        self._max_tasks = len(self._list_info_medias) + len(
            self._list_of_sentences)

    def getCollection(self) -> Filename:
        """
        Returns the filename for the anki2.collection.

        :return: Filename of the anki2.collection.
        """

        return self._collection_filename

    def getDeckName(self) -> str:
        """
        Returns the deck name.

        :return: Deck name.
        """

        return self._deck_name

    def getVideoFilename(self) -> Filename:
        """
        Returns the name of the video file.

        :return: Video filename.
        """

        return self._video_filename

    def getListInfoMedias(self) -> List[List[Info]]:
        """
        Returns a list with information about each media to be used at creating cards.

        :return: A list with information about each media.
        """

        return self._list_info_medias

    def getListOfSentences(self) -> ListSentences:
        """
        Returns a List with information about each sentence to be used at creating cards.

        :return: A list with information about each sentence.
        """

        return self._list_of_sentences

    def appendFuture(self, future: Future) -> None:
        """
        Append the future to _futures_list.

        :return:
        """

        self._futures_list.append(future)
    def __init__(self, extensions):
        # Start threads
        threads_init()

        self.extensions = extensions

        # Create the main Bluemindo window
        self.main_window = Window()
        functions.open_bluemindo(self.main_window)

        # Handling close button
        def close_window(wdg, ka):
            functions.close_bluemindo(self.main_window, True)

        self.main_window.connect('delete_event', close_window)

        # Create the whole Header Bar
        box = HeaderBar()
        box.set_show_close_button(True)
        box.props.title = 'Bluemindo'
        self.main_window.set_titlebar(box)

        # Add an icon to the window
        icon_file = join(functions.datadir, 'image', 'logo_head_small.png')
        pixbuf = Pixbuf.new_from_file(icon_file)
        self.main_window.set_icon(pixbuf)

        # Add the about button
        about_button = Button(relief=2)
        about_button.add(
            Image.new_from_gicon(ThemedIcon(name='help-about-symbolic'),
                                 IconSize.BUTTON))
        box.pack_end(about_button)

        # Add the reload button
        refresh_button = Button(relief=2)
        refresh_button.add(
            Image.new_from_gicon(ThemedIcon(name='view-refresh-symbolic'),
                                 IconSize.BUTTON))
        box.pack_end(refresh_button)

        # Add PREVIOUS/STOP/PLAYPAUSE/NEXT buttons
        player_box = Box(orientation=Orientation.HORIZONTAL)
        StyleContext.add_class(player_box.get_style_context(), 'linked')

        previous_b = Button()
        previous_b.set_size_request(42, -1)
        previous_b.add(
            Image.new_from_gicon(
                ThemedIcon(name='media-skip-backward-symbolic'),
                IconSize.BUTTON))
        player_box.add(previous_b)

        stop_b = Button()
        stop_b.set_size_request(42, -1)
        stop_b.add(
            Image.new_from_gicon(
                ThemedIcon(name='media-playback-stop-symbolic'),
                IconSize.BUTTON))
        player_box.add(stop_b)

        playpause_b = Button()
        playpause_b.set_size_request(55, -1)
        playpause_b.add(
            Image.new_from_gicon(
                ThemedIcon(name='media-playback-start-symbolic'),
                IconSize.BUTTON))
        player_box.add(playpause_b)

        next_b = Button()
        next_b.set_size_request(42, -1)
        next_b.add(
            Image.new_from_gicon(
                ThemedIcon(name='media-skip-forward-symbolic'),
                IconSize.BUTTON))
        player_box.add(next_b)

        box.pack_start(player_box)

        # Create the main window
        glade_main = join(functions.datadir, 'glade', 'mainwindow.ui')
        win = gtk_builder()
        win.set_translation_domain('bluemindo')
        win.add_from_file(glade_main)

        self.main_window.add(win.get_object('box1'))

        # Connect to the about button
        def show_dialog(wdg):
            dialog = AboutDialog()
            dialog.set_transient_for(self.main_window)

            dialog.set_artists(['Thomas Julien <*****@*****.**>'])
            dialog.set_authors([
                'Erwan Briand <*****@*****.**>',
                'Vincent Berset <*****@*****.**>',
                'Thibaut Girka <*****@*****.**>',
                'Ľubomír Remák <*****@*****.**>',
                'Anaël Verrier <*****@*****.**>'
            ])
            dialog.set_translator_credits(
                'Bruno Conde <*****@*****.**>\n' +
                'Niklas Grahn <*****@*****.**>\n' +
                'Ľubomír Remák <*****@*****.**>\n' +
                'Salvatore Tomarchio <*****@*****.**>\n' +
                'Shang Yuanchun <*****@*****.**>')

            dialog.set_copyright('Copyright © 2007-2016 Erwan Briand ' +
                                 '<*****@*****.**>')

            dialog.set_comments(
                _('Ergonomic and modern music player ' +
                  'designed for audiophiles.'))

            dialog.set_license('GNU General Public License (v3)')
            dialog.set_license_type(10)

            dialog.set_program_name('Bluemindo')
            dialog.set_version('1.0RC1')
            dialog.set_website('http://bluemindo.codingteam.net')

            pxbf = Pixbuf.new_from_file_at_scale(
                join(functions.datadir, 'image', 'logo_head_big.png'), 60, 60,
                True)
            dialog.set_logo(pxbf)

            dialog.show_all()

        about_button.connect('clicked', show_dialog)

        # Start main handler
        headerbar_wdg = [
            box, None, about_button, refresh_button, player_box, previous_b,
            stop_b, playpause_b, next_b, None,
            win.get_object('box1'), self.main_window
        ]
        self.wdg = [headerbar_wdg, win]
Esempio n. 4
0
class FilesChooser(Window):
    def __init__(self, app: Application):
        super().__init__(title='Asts', application=app)

        self._app: Application = app

        self.set_default_size(1000, 700)
        self.set_keep_above(True)
        self.set_resizable(False)

        self._box: Box = Box()
        self._box.set_orientation(Orientation.VERTICAL)

        setBgColor(widget=self, alpha=0.93)

        setMargin(self._box, 10)

        self._fst_grid: Grid = Grid()

        # labels
        self._setLabels()

        # file choosers button
        # _fc_s[0] = anki2.collection file
        # _fc_s[1] = video file
        # _fc_s[2] = subtitle file
        # _fc_s[3] = optional subtitle file
        self._fc_s: List[FileChooser] = self._setFileChoosers()

        # text entry
        self._entry: Entry
        self._setTextEntry()

        self._fillCachedFile()

        # filters
        self._setFilters()

        # buttons
        self._next_btn: Button
        self._setButtonsSignals()

        # box.pack_(child, expand, fill, padding)
        self._box.pack_start(self._fst_grid, False, True, 0)

        self.add(self._box)

    def _setLabels(self) -> None:
        """
        Setup the labels for the grid.

        :return:
        """

        labels: List[Label] = [
            Label(label='collection.anki2 File (Required):'),
            Label(label='Video (Required):'),
            Label(label='Subtitle File with The Target Language (Required):'),
            Label(label='Subtitle File with Translation (Optional):'),
            Label(label='Deck Name (Required):')
        ]

        for (idx, lbl) in enumerate(labels):
            lbl.set_halign(Align.START)

            # Grid.attach(child, left, top, width, height)
            self._fst_grid.attach(lbl, 0, idx, 1, 1)

    def _setFileChoosers(self) -> List[FileChooser]:
        """
        Set up the file choosers for the grid.

        :return: The file choosers created.
        """

        f_cs: List[FileChooser] = [FileChooserButton() for _ in range(4)]

        for (idx, f_c) in enumerate(f_cs):
            f_c.set_hexpand(True)
            setMargin(f_c, 10, 5, 10, 5)

            f_c.set_halign(Align.FILL)

            self._fst_grid.attach(f_c, 1, idx, 1, 1)

        return f_cs

    def _setFilters(self) -> None:
        """
        Set a filter for each file chooser.

        :return: 
        """

        ff1: FileFilter = FileFilter()
        ff1.set_name('collection.anki2')
        ff1.add_pattern('*.anki2')

        self._fc_s[FileType.ANKI2_COLLECTION].add_filter(ff1)

        ff2: FileFilter = FileFilter()
        ff2.set_name('Video File')
        ff2.add_mime_type('video/mp4')
        ff2.add_mime_type('video/wmv')
        ff2.add_mime_type('video/avi')
        ff2.add_mime_type('video/mkv')
        ff2.add_mime_type('video/webm')
        ff2.add_pattern('*.mp4')
        ff2.add_pattern('*.wmv')
        ff2.add_pattern('*.avi')
        ff2.add_pattern('*.mkv')
        ff2.add_pattern('*.webm')

        self._fc_s[FileType.VIDEO].add_filter(ff2)

        ff3: FileFilter = FileFilter()
        ff3.set_name('Subtitles (ASS/SRT)')
        ff3.add_pattern('*.srt')
        ff3.add_pattern('*.ass')

        self._fc_s[FileType.SUBTITLE].add_filter(ff3)
        self._fc_s[FileType.O_SUBTITLE].add_filter(ff3)

    def _fillCachedFile(self) -> None:
        """
        Fills up collection_file and deck_name filename.

        :return:
        """

        try:
            cache_dir: Filepath = path.abspath('data/cache')
            cached_usage: Filename = path.join(cache_dir + '/' +
                                               'cached_usage.txt')

            with open(cached_usage, 'r') as f:
                list_cache: List[str] = f.read().split('\n')
                self._fc_s[FileType.ANKI2_COLLECTION].set_filename(
                    list_cache[0])
                self._entry.set_text(list_cache[1])
        # it's safe to pass here
        # it means that there's no filename cached to be used
        except FileNotFoundError:
            pass

    def _setButtonsSignals(self) -> None:
        """
        Set up the buttons and their respective signals for both grids.

        :param win: A window.
        :param fst_grid: The first grid container where the buttons goes in.
        :param box: The box container where the first grid goes in.
        :param fc_s: A list with file choosers.
        :return:
        """

        del_img: Optional[Pixbuf]

        img_path: str = path.abspath('asts/Icons/delete.png')

        try:
            del_img = Pixbuf().new_from_file_at_scale(img_path, 20, 20, False)
        except GLib_Error:
            exit(f'{img_path} file not found. Failed to create pixbuf.')

        icons: List[Optional[Image]] = [
            Image().new_from_pixbuf(del_img) for _ in range(4)
        ]

        btns: List[Button] = [Button() for _ in range(4)]

        for (idx, btn) in enumerate(btns):
            btn.set_image(icons[idx])
            setMargin(btn, 0, 5, 0, 5)
            btn.set_halign(Align.END)

            self._fst_grid.attach(btn, 2, idx, 1, 1)

        btns[FileType.ANKI2_COLLECTION].connect(
            'clicked',
            lambda _: self._fc_s[FileType.ANKI2_COLLECTION].unselect_all())

        btns[FileType.VIDEO].connect(
            'clicked', lambda _: self._fc_s[FileType.VIDEO].unselect_all())

        btns[FileType.SUBTITLE].connect(
            'clicked', lambda _: self._fc_s[FileType.SUBTITLE].unselect_all())

        btns[FileType.O_SUBTITLE].connect(
            'clicked',
            lambda _: self._fc_s[FileType.O_SUBTITLE].unselect_all())

        box: Box = Box()

        self._box.pack_end(box, False, True, 0)

        box.set_orientation(Orientation.HORIZONTAL)
        box.set_halign(Align.CENTER)

        cancel_btn: Button = Button(label='Cancel')

        box.pack_start(cancel_btn, False, True, 0)

        cancel_btn.set_margin_bottom(10)
        cancel_btn.connect('clicked', lambda _: self.close())

        self._next_btn = Button(label='Next')

        box.pack_end(self._next_btn, False, True, 0)

        setMargin(self._next_btn, 200, 10, 0, 0)
        self._next_btn.connect('clicked', self._onNextBtnClicked)

        timeout_add(300, self._setSensitiveNextBtn)
        timeout_add(300, self._checkInvalidSelection)

    def _setTextEntry(self) -> None:
        """
        Sets the text entry for deck name.

        :return:
        """

        self._entry = Entry(placeholder_text='Deck name...')

        setMargin(self._entry, 10, 5, 10, 5)

        self._fst_grid.attach(self._entry, 1, 4, 1, 1)

    def _getFilename(self, f_type: FileType) -> Optional[str]:
        """
        Return the name of the file.

        :param f_type: The type of file.
        :return: The name of the file.
        """

        try:
            return self._fc_s[f_type].get_filename()
        except IndexError:
            return None

    def _getDeckName(self) -> str:
        """
        Gets the deck name.

        :return: The deck name.
        """

        return self._entry.get_text()

    def _checkInvalidSelection(self) -> bool:
        """
        Unselects any invalid filename selected through file choosers.

        :return: True to keep timeout_add running.
        """

        col: Optional[str] = self._getFilename(FileType.ANKI2_COLLECTION)
        vid: Optional[str] = self._getFilename(FileType.VIDEO)
        sub: Optional[str] = self._getFilename(FileType.SUBTITLE)
        o_sub: Optional[str] = self._getFilename(FileType.O_SUBTITLE)

        if col != None and not isCollection(col):
            self._fc_s[0].unselect_all()

        if vid != None and not isVideo(vid):
            self._fc_s[1].unselect_all()

        if sub != None and not isSub(sub):
            self._fc_s[2].unselect_all()

        if o_sub != None and not isSub(o_sub):
            self._fc_s[3].unselect_all()

        return True

    def _setSensitiveNextBtn(self) -> bool:
        """
        Set if a button is clickable or not.

        :return: True to keep timeout_add running.
        """

        col: bool = isCollection(self._getFilename(FileType.ANKI2_COLLECTION))
        vid: bool = isVideo(self._getFilename(FileType.VIDEO))
        sub: bool = isSub(self._getFilename(FileType.SUBTITLE))
        deck_name: str = self._getDeckName()

        if all((col, vid, sub, deck_name)):
            self._next_btn.set_sensitive(True)
        else:
            self._next_btn.set_sensitive(False)

        return True

    def _onNextBtnClicked(self, _) -> None:
        """
        Opens the generator of anki cards.

        :return:
        """

        createCacheDirIfItNotExists()

        clearCachedFiles()

        recentUsedFiles(self._getFilename(FileType.ANKI2_COLLECTION),
                        self._getDeckName())

        CardsGenerator(self, self._app,
                       self._getFilename(FileType.ANKI2_COLLECTION),
                       self._getFilename(FileType.VIDEO),
                       self._getFilename(FileType.SUBTITLE),
                       self._getFilename(FileType.O_SUBTITLE),
                       self._getDeckName()).showAll()