import os import weakref import logging logger = logging.getLogger(__name__) from core import cons from core import events from core import utils from core.config import conf from core.api import api from PySide.QtGui import * from PySide.QtCore import * import media import signals from list_model import SimpleListModel from context_menu import Menu class Downloads(QTreeView): def __init__(self, parent): #TODO: Create wrapper or subclass list to append and remove from items and rows_buffer. QTreeView.__init__(self) self.weak_parent = weakref.ref(parent) #listview look self.setWordWrap(True) #search textElideMode self.setRootIsDecorated(False) self.setIndentation(0) self.setAlternatingRowColors(True) self.icons_dict = self.get_icons() self.items = [] self.rows_buffer = {} #{id_item: row_obj, } headers = ["hidden_id_item", "", _("File Name"), _("Host"), _("Size"), _("Complete"), _("Progress"), _("Time"), _("Remain"), _("Speed"), _("Status Message")] self.__model = SimpleListModel(headers, self.items) self.setModel(self.__model) self.setColumnHidden(0, True) self.setColumnWidth(1, 27) self.header().setResizeMode(1, QHeaderView.Fixed) #self.header().setResizeMode(3, QHeaderView.ResizeToContents) self.im_delegate = ImageDelegate(self) self.setItemDelegateForColumn(1, self.im_delegate) self.im2_delegate = ImageDelegate(self) self.setItemDelegateForColumn(3, self.im2_delegate) self.nf_delegate = NoFocusDelegate(self) self.setItemDelegate(self.nf_delegate) self.pb_delegate = ProgressBarDelegate(self) self.setItemDelegateForColumn(6, self.pb_delegate) #see http://stackoverflow.com/questions/1987546/qt4-stylesheets-and-focus-rect for removing the drawed border on focus self.setSelectionMode(QAbstractItemView.ExtendedSelection) #self.setDragEnabled(True) #self.setAcceptDrops(True) #self.setDropIndicatorShown(False) self.setDragDropMode(QAbstractItemView.InternalMove) #self.setDefaultDropAction(Qt.MoveAction) #drop indicator self.line = QWidget(self.viewport()) self.line.setAutoFillBackground(True) pal = self.line.palette() pal.setColor(self.line.backgroundRole(), Qt.black) self.line.setPalette(pal) self.line.setGeometry(0, 0, 0, 0) self.line.hide() #custom signals signals.add_to_downloader.connect(self.add_to_downloader) signals.on_stop_all.connect(self.on_stop_all) #update list self.timer = parent.idle_timeout(1000, self.update_) @property def parent(self): return self.weak_parent() def remove_row(self, id_item): item = self.rows_buffer.pop(id_item) self.__model.remove(self.items.index(item)) def get_selected_rows(self): """""" selected_rows = [index.row() for index in self.selectionModel().selectedRows()] selected_rows.sort() return selected_rows def dragEnterEvent(self, event): #FIXME: weird things may happen if draggin when an item is removed or added. #event.setDropAction(Qt.MoveAction) event.accept() def dragMoveEvent(self, event): #paint drop indicator at the current position. q_index = self.indexAt(event.pos()) if q_index.isValid(): rect = self.visualRect(q_index) self.line.setGeometry(0, rect.top(), self.viewport().width(), 1) else: q_index = self.__model.index(self.__model.rowCount() - 1) rect = self.visualRect(q_index) self.line.setGeometry(0, rect.bottom(), self.viewport().width(), 1) self.line.show() def dragLeaveEvent(self, event): #out of the drop area. self.line.hide() def dropEvent(self, event): self.line.hide() q_index = self.indexAt(event.pos()) if q_index.isValid(): index = q_index.row() else: index = -1 items = [self.items[row] for row in self.get_selected_rows()] dest_item = self.items[index] [self.__model.remove(self.items.index(item)) for item in items if item != dest_item] if index >= 0: index = self.items.index(dest_item) [self.__model.insert(index, item) for item in reversed(items) if item != dest_item] else: [self.__model.append(item) for item in items if item != dest_item] api.reorder_queue([row[0] for row in self.items]) #def keyboard_event(self, widget, event): #if gtk.gdk.keyval_name(event.keyval) == "Delete": #supr. key #self.on_delete() def contextMenuEvent(self, event): rows = self.get_selected_rows() is_selection = True if rows else False options = [(_('Open destination folder'), self.on_open_folder, is_selection), (_('Copy link'), self.on_copy_link, is_selection), #(_('Add password'), self.on_password, is_selection), None, (_('Delete'), self.on_delete, is_selection), None, (_('Start all'), self.on_start_all, True), (_('Stop all'), self.on_stop_all, True), (_('Clear Completed'), self.on_clear_completed, True)] menu = Menu(options) menu.exec_(event.globalPos()) def selectionChanged(self, selected, deselected): #overridden slot QTreeView.selectionChanged(self, selected, deselected) BTN = 0 rows = self.get_selected_rows() if len(rows) == 1: # single selection. row_index = rows[0] id_item = self.items[row_index][0] stopped_downloads = api.get_stopped_downloads() if id_item in stopped_downloads: self.parent.stop[BTN].setEnabled(False) self.parent.start[BTN].setEnabled(True) else: self.parent.stop[BTN].setEnabled(True) self.parent.start[BTN].setEnabled(False) elif rows: # multi selection self.parent.stop[BTN].setEnabled(True) self.parent.start[BTN].setEnabled(True) else: # no selection self.parent.stop[BTN].setEnabled(False) self.parent.start[BTN].setEnabled(False) def on_open_folder(self): rows = self.get_selected_rows() if rows: items_list = api.get_download_items([self.items[row_index][0] for row_index in rows]) paths_list = {download_item.path for download_item in items_list} for folder_path in paths_list: utils.open_folder_window(folder_path) def on_copy_link(self): rows = self.get_selected_rows() if rows: items_list = api.get_download_items([self.items[row_index][0] for row_index in rows]) links_list = [download_item.link for download_item in items_list if download_item.can_copy_link] clipboard = QApplication.clipboard() clipboard.setText('\n'.join(links_list)) def on_password(self): rows = self.get_selected_rows() if rows: pass #entry = gtk.Entry() #entry.add_events(gtk.gdk.KEY_RELEASE_MASK) #entry.set_width_chars(25) #entry width #m = DlgGui(self.__parent, None, _("Password"), None, True, append_widget=entry) #pwd = entry.get_text().strip() #if m.accepted and pwd: #events.add_password.emit(pwd) def on_delete(self): rows = self.get_selected_rows() if rows: #message = _("Do you want to remove this download? (downloaded segments will be deleted)") #m = DlgGui(self.__parent, gtk.STOCK_DIALOG_WARNING, _("Remove Files"), message, True, True) m = True if m: id_items_list = [] for row_index in rows: id_item = self.items[row_index][0] id_items_list.append(id_item) [self.remove_row(id_item) for id_item in id_items_list] api.delete_download(id_items_list) def on_clear_completed(self): finished_icon = self.icons_dict[cons.STATUS_FINISHED] for row in self.items[:]: if row[1] == finished_icon: self.remove_row(row[0]) api.clear_complete() def on_start_all(self): """ BUG: El boton start y stop no cambia. """ id_item_list = [row[0] for row in self.items] api.start_all(id_item_list) stopped_icon = self.icons_dict[cons.STATUS_STOPPED] queue_icon = self.icons_dict[cons.STATUS_QUEUE] for row in self.items: if row[1] == stopped_icon: row[1] = queue_icon def on_stop_all(self): api.stop_all() stopped_icon = self.icons_dict[cons.STATUS_STOPPED] queue_icon = self.icons_dict[cons.STATUS_QUEUE] for row in self.items: if row[1] == queue_icon: row[1] = stopped_icon def add_to_downloader(self, download_items): for download_item in download_items: api.add_to_downloader(download_item) self.store_item(download_item) if conf.get_auto_switch_tab(): signals.switch_tab.emit(0) api.next_download() def store_item(self, download_item): size_file = utils.size_format(download_item.size) if download_item.size else None size_complete = utils.size_format(download_item.size_complete) if download_item.size_complete else None time = utils.time_format(download_item.time) if download_item.time else None host_icon = self.get_host_icon(download_item.host) item = [download_item.id, self.icons_dict[download_item.status], download_item.name, [host_icon, None, None], size_file, size_complete, download_item.progress, time, None, None, download_item.status_msg] self.__model.append(item) self.rows_buffer[item[0]] = item def update_(self): active_downloads = api.get_active_downloads() api.update_active_downloads() for download_item in active_downloads.itervalues(): row = self.rows_buffer[download_item.id] #row[0] = download_item.id row[1] = self.icons_dict[download_item.status] row[2] = download_item.name #row[3][0] = download_item.host row[3][1] = self.icons_dict[cons.DL_RESUME] if download_item.can_resume else None row[3][2] = self.icons_dict[cons.DL_PREMIUM] if download_item.is_premium else None row[4] = utils.size_format(download_item.size) if download_item.size else None row[5] = utils.size_format(download_item.size_complete) if download_item.size_complete else None row[6] = download_item.progress row[7] = utils.time_format(download_item.time) if download_item.time else None row[8] = utils.time_format(download_item.time_remain) if download_item.time_remain else None row[9] = utils.speed_format(download_item.speed) if download_item.speed else None row[10] = self.get_status_msg(download_item) self.__model.refresh() def get_status_msg(self, download_item): if download_item.fail_count: return "{0} ({1} #{2})".format(download_item.status_msg, _("Retry"), download_item.fail_count) else: return download_item.status_msg def get_host_icon(self, host): try: return self.icons_dict[host] except KeyError: self.icons_dict[host] = QPixmap(os.path.join(cons.PLUGINS_PATH, host.replace('.', '_'), "favicon.ico")) return self.icons_dict[host] def get_icons(self): running = media.get_pixmap(media.START, media.SMALL) stopped = media.get_pixmap(media.STOP, media.SMALL) queue = media.get_pixmap(media.QUEUE, media.SMALL) finished = media.get_pixmap(media.CHECK, media.SMALL) error = media.get_pixmap(media.X_MARK, media.SMALL) resume = media.get_pixmap(media.REFRESH, media.SMALL) premium = media.get_pixmap(media.ACCOUNTS, media.SMALL) return {cons.STATUS_RUNNING: running, cons.STATUS_STOPPED: stopped, cons.STATUS_QUEUE: queue, cons.STATUS_FINISHED: finished, cons.STATUS_ERROR: error, cons.DL_RESUME: resume, cons.DL_PREMIUM: premium} class NoFocusDelegate(QStyledItemDelegate): def __init__(self, parent): QStyledItemDelegate.__init__(self, parent) def paint(self, painter, option, index): if (option.state & QStyle.State_HasFocus): option.state ^= QStyle.State_HasFocus QStyledItemDelegate.paint(self, painter, option, index) class ImageDelegate(QStyledItemDelegate): def __init__(self, parent): QStyledItemDelegate.__init__(self, parent) def sizeHint(self, option, index): return QSize(-1, 16) def paint(self, painter, option, index): if (option.state & QStyle.State_HasFocus): option.state ^= QStyle.State_HasFocus QStyledItemDelegate.paint(self, painter, option, index) painter.save() #rect = option.rect #rect = QRect(rect.left() + w_margin, rect.top() + h_margin, 16, 16) #center rect = option.rect width = 16 #rect.height() height = 16 #rect.height() #w_margin = max(0, rect.width() - width) / 2 h_margin = max(0, rect.height() - height) / 2 x = rect.left() y = rect.top() + h_margin margin = 2 data = index.data() #pix (col 1) or list of pix (col 3) if data: if index.column() == 1: w_margin = max(0, rect.width() - width) / 2 painter.drawPixmap(QRect(x + w_margin, y, width, height), data) else: for pix in data: if pix is not None: painter.drawPixmap(QRect(x, y, width, height), pix) x += width + margin painter.restore() class ProgressBarDelegate(QStyledItemDelegate): def __init__(self, parent): QStyledItemDelegate.__init__(self, parent=parent) #def sizeHint(self, option, index): #return QSize(-1, 26) def paint(self, painter, option, index): if (option.state & QStyle.State_HasFocus): option.state ^= QStyle.State_HasFocus QStyledItemDelegate.paint(self, painter, option, index) painter.save() #if not index.isValid(): #return None #painter.save() #if index.column() == 2: #if option.state & QStyle.State_Selected: #paint background if selected. #painter.fillRect(option.rect, painter.brush()) progress = index.data() bar_option = QStyleOptionProgressBarV2() bar_option.rect = option.rect bar_option.rect.setHeight(option.rect.height() - 1) bar_option.rect.setTop(option.rect.top() + 1) bar_option.minimum = 0 bar_option.maximum = 100 bar_option.progress = progress bar_option.text = str(progress) + '%' bar_option.textVisible = True bar_option.textAlignment = Qt.AlignCenter QApplication.style().drawControl(QStyle.CE_ProgressBar, bar_option, painter) #else: #QStyledItemDelegate.paint(self, painter, option, index) painter.restore()