mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-04 03:27:00 -05:00 
			
		
		
		
	More work on the connect to folder dialog
This commit is contained in:
		
							parent
							
								
									c4de6b9bd9
								
							
						
					
					
						commit
						9684457744
					
				@ -674,7 +674,7 @@ def device_plugins(include_disabled=False):
 | 
			
		||||
def usbms_plugins(include_disabled=True):
 | 
			
		||||
    from calibre.devices.usbms.driver import USBMS
 | 
			
		||||
    for plugin in device_plugins(include_disabled):
 | 
			
		||||
        if isinstance(plugin, USBMS) and plugin.name not in ('Folder Device Interface',):
 | 
			
		||||
        if isinstance(plugin, USBMS) and plugin.name not in ('Folder Device Interface', 'User Defined USB driver'):
 | 
			
		||||
            yield plugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -293,7 +293,7 @@ class INVESBOOK(EB600):
 | 
			
		||||
 | 
			
		||||
class BOOQ(EB600):
 | 
			
		||||
    name = 'Booq Device Interface'
 | 
			
		||||
    gui_name = 'bq Reader'
 | 
			
		||||
    gui_name = 'Bq Reader'
 | 
			
		||||
 | 
			
		||||
    FORMATS = ['epub', 'mobi', 'prc', 'fb2', 'pdf', 'doc', 'rtf', 'txt', 'html']
 | 
			
		||||
 | 
			
		||||
@ -330,6 +330,7 @@ class ELONEX(EB600):
 | 
			
		||||
class POCKETBOOK301(USBMS):
 | 
			
		||||
 | 
			
		||||
    name           = 'PocketBook 301 Device Interface'
 | 
			
		||||
    gui_name = 'PocketBook 301'
 | 
			
		||||
    description    = _('Communicate with the PocketBook 301 Reader.')
 | 
			
		||||
    author         = 'Kovid Goyal'
 | 
			
		||||
    supported_platforms = ['windows', 'osx', 'linux']
 | 
			
		||||
@ -348,7 +349,7 @@ class POCKETBOOK301(USBMS):
 | 
			
		||||
class POCKETBOOK602(USBMS):
 | 
			
		||||
 | 
			
		||||
    name = 'PocketBook Pro 602/902 Device Interface'
 | 
			
		||||
    gui_name = 'PocketBook'
 | 
			
		||||
    gui_name = 'PocketBook Pro'
 | 
			
		||||
    description    = _('Communicate with the PocketBook 515/602/603/902/903/Pro 912 reader.')
 | 
			
		||||
    author         = 'Kovid Goyal'
 | 
			
		||||
    supported_platforms = ['windows', 'osx', 'linux']
 | 
			
		||||
@ -371,6 +372,7 @@ class POCKETBOOK602(USBMS):
 | 
			
		||||
class POCKETBOOK622(POCKETBOOK602):
 | 
			
		||||
 | 
			
		||||
    name = 'PocketBook 622 Device Interface'
 | 
			
		||||
    gui_name = 'PocketBook 622'
 | 
			
		||||
    description    = _('Communicate with the PocketBook 622 and 623 readers.')
 | 
			
		||||
    EBOOK_DIR_MAIN = ''
 | 
			
		||||
 | 
			
		||||
@ -385,6 +387,7 @@ class POCKETBOOK622(POCKETBOOK602):
 | 
			
		||||
class POCKETBOOK360P(POCKETBOOK602):
 | 
			
		||||
 | 
			
		||||
    name = 'PocketBook 360+ Device Interface'
 | 
			
		||||
    gui_name = 'PocketBook 360+'
 | 
			
		||||
    description    = _('Communicate with the PocketBook 360+ reader.')
 | 
			
		||||
    BCD         = [0x0323]
 | 
			
		||||
    EBOOK_DIR_MAIN = ''
 | 
			
		||||
@ -396,7 +399,7 @@ class POCKETBOOK360P(POCKETBOOK602):
 | 
			
		||||
class POCKETBOOK701(USBMS):
 | 
			
		||||
 | 
			
		||||
    name = 'PocketBook 701 Device Interface'
 | 
			
		||||
    gui_name = 'PocketBook'
 | 
			
		||||
    gui_name = 'PocketBook 701'
 | 
			
		||||
    description = _('Communicate with the PocketBook 701')
 | 
			
		||||
    author = _('Kovid Goyal')
 | 
			
		||||
 | 
			
		||||
@ -428,7 +431,7 @@ class POCKETBOOK701(USBMS):
 | 
			
		||||
class POCKETBOOK740(USBMS):
 | 
			
		||||
 | 
			
		||||
    name = 'PocketBook 740 Device Interface'
 | 
			
		||||
    gui_name = 'PocketBook'
 | 
			
		||||
    gui_name = 'PocketBook 740'
 | 
			
		||||
    description = _('Communicate with the PocketBook 740')
 | 
			
		||||
    supported_platforms = ['windows', 'osx', 'linux']
 | 
			
		||||
    FORMATS = ['epub', 'fb2', 'prc', 'mobi', 'pdf', 'djvu', 'rtf', 'chm',
 | 
			
		||||
 | 
			
		||||
@ -61,7 +61,7 @@ class KIBANO(N516):
 | 
			
		||||
 | 
			
		||||
class THEBOOK(N516):
 | 
			
		||||
    name = 'The Book driver'
 | 
			
		||||
    gui_name = 'The Book'
 | 
			
		||||
    gui_name = 'Book'
 | 
			
		||||
    description    = _('Communicate with The Book reader.')
 | 
			
		||||
    author         = 'Kovid Goyal'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,10 @@ class ModelMetadata(NamedTuple):
 | 
			
		||||
    bcd: int
 | 
			
		||||
    driver_class: type
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def settings_key(self) -> str:
 | 
			
		||||
        return f'{self.manufacturer_name} - {self.model_name}'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OpenPopupMessage:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -82,7 +82,7 @@ def get_files_in(path):
 | 
			
		||||
class KINDLE(USBMS):
 | 
			
		||||
 | 
			
		||||
    name           = 'Kindle Device Interface'
 | 
			
		||||
    gui_name       = 'Amazon Kindle'
 | 
			
		||||
    gui_name       = 'Amazon Kindle Keyboard'
 | 
			
		||||
    icon           = 'devices/kindle.png'
 | 
			
		||||
    description    = _('Communicate with the Kindle e-book reader.')
 | 
			
		||||
    author         = 'John Schember'
 | 
			
		||||
@ -369,6 +369,7 @@ class KINDLE(USBMS):
 | 
			
		||||
class KINDLE2(KINDLE):
 | 
			
		||||
 | 
			
		||||
    name           = 'Kindle 2/3/4/Touch/PaperWhite/Voyage Device Interface'
 | 
			
		||||
    gui_name = 'Amazon Kindle'
 | 
			
		||||
    description    = _('Communicate with the Kindle 2/3/4/Touch/Paperwhite/Voyage e-book reader.')
 | 
			
		||||
 | 
			
		||||
    FORMATS     = ['azw', 'mobi', 'azw3', 'prc', 'azw1', 'tpz', 'azw4', 'kfx', 'pobi', 'pdf', 'txt']
 | 
			
		||||
@ -649,6 +650,7 @@ class KINDLE2(KINDLE):
 | 
			
		||||
class KINDLE_DX(KINDLE2):
 | 
			
		||||
 | 
			
		||||
    name           = 'Kindle DX Device Interface'
 | 
			
		||||
    gui_name       = 'Amazon Kindle DX'
 | 
			
		||||
    description    = _('Communicate with the Kindle DX e-book reader.')
 | 
			
		||||
 | 
			
		||||
    FORMATS = ['azw', 'mobi', 'prc', 'azw1', 'tpz', 'azw4', 'pobi', 'pdf', 'txt']
 | 
			
		||||
@ -666,7 +668,7 @@ class KINDLE_FIRE(KINDLE2):
 | 
			
		||||
 | 
			
		||||
    name = 'Kindle Fire Device Interface'
 | 
			
		||||
    description = _('Communicate with the Kindle Fire')
 | 
			
		||||
    gui_name = 'Fire'
 | 
			
		||||
    gui_name = 'Amazon Kindle Fire'
 | 
			
		||||
    FORMATS = ['azw3', 'azw', 'mobi', 'prc', 'azw1', 'tpz', 'azw4', 'kfx', 'pobi', 'pdf', 'txt']
 | 
			
		||||
 | 
			
		||||
    PRODUCT_ID = [0x0006]
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ class PALMPRE(USBMS):
 | 
			
		||||
 | 
			
		||||
class AVANT(USBMS):
 | 
			
		||||
    name           = 'Booq Avant Device Interface'
 | 
			
		||||
    gui_name       = 'bq Avant'
 | 
			
		||||
    gui_name       = 'Bq Avant'
 | 
			
		||||
    description    = _('Communicate with the Bq Avant')
 | 
			
		||||
    author         = 'Kovid Goyal'
 | 
			
		||||
    supported_platforms = ['windows', 'osx', 'linux']
 | 
			
		||||
@ -521,7 +521,7 @@ class WOXTER(USBMS):
 | 
			
		||||
class POCKETBOOK626(USBMS):
 | 
			
		||||
 | 
			
		||||
    name  = 'PocketBook Touch Lux 2'
 | 
			
		||||
    gui_name = 'PocketBook'
 | 
			
		||||
    gui_name = 'PocketBook Touch Lux 2'
 | 
			
		||||
    description    = _('Communicate with the PocketBook Touch Lux 2 and Inkpad X readers')
 | 
			
		||||
    author         = 'Kovid Goyal'
 | 
			
		||||
    supported_platforms = ['windows', 'osx', 'linux']
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ from calibre.utils.resources import get_image_path as I
 | 
			
		||||
class NOOK(USBMS):
 | 
			
		||||
 | 
			
		||||
    name           = 'Nook Device Interface'
 | 
			
		||||
    gui_name       = _('The Nook')
 | 
			
		||||
    gui_name       = 'B&N Nook'
 | 
			
		||||
    description    = _('Communicate with the Nook e-book reader.')
 | 
			
		||||
    author         = 'John Schember'
 | 
			
		||||
    icon           = 'devices/nook.png'
 | 
			
		||||
@ -80,6 +80,7 @@ class NOOK(USBMS):
 | 
			
		||||
 | 
			
		||||
class NOOK_COLOR(NOOK):
 | 
			
		||||
    name           = 'Nook Color Device Interface'
 | 
			
		||||
    gui_name = _('B&N Nook Color')
 | 
			
		||||
    description    = _('Communicate with the Nook Color, TSR, Glowlight and Tablet e-book readers.')
 | 
			
		||||
 | 
			
		||||
    PRODUCT_ID  = [
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ from calibre.prints import debug_print
 | 
			
		||||
class PRS505(USBMS):
 | 
			
		||||
 | 
			
		||||
    name           = 'SONY Device Interface'
 | 
			
		||||
    gui_name       = 'SONY Reader'
 | 
			
		||||
    gui_name       = 'SONY PRS-500'
 | 
			
		||||
    description    = _('Communicate with Sony e-book readers older than the'
 | 
			
		||||
            ' PRST1.')
 | 
			
		||||
    author         = 'Kovid Goyal'
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,7 @@ class ImageWrapper:
 | 
			
		||||
 | 
			
		||||
class PRST1(USBMS):
 | 
			
		||||
    name           = 'SONY PRST1 and newer Device Interface'
 | 
			
		||||
    gui_name       = 'SONY Reader'
 | 
			
		||||
    gui_name       = 'SONY PRS-T1'
 | 
			
		||||
    description    = _('Communicate with the PRST1 and newer SONY e-book readers')
 | 
			
		||||
    author         = 'Kovid Goyal'
 | 
			
		||||
    supported_platforms = ['windows', 'osx', 'linux']
 | 
			
		||||
 | 
			
		||||
@ -150,7 +150,10 @@ class Device(DeviceConfig, DevicePlugin):
 | 
			
		||||
                bcd = cls.BCD
 | 
			
		||||
            return vid or 0, pid or 0, bcd or 0
 | 
			
		||||
        vid, pid, bcd = get_representative_ids()
 | 
			
		||||
        try:
 | 
			
		||||
            model_name = cls.get_gui_name()
 | 
			
		||||
        except TypeError:  # The WAYTEQ driver implements this as non classmethod
 | 
			
		||||
            return ()
 | 
			
		||||
        parts = model_name.split(' ', 1)
 | 
			
		||||
        manufacturer = ''
 | 
			
		||||
        if len(parts) > 1:
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,130 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# License: GPLv3 Copyright: 2025, Kovid Goyal <kovid at kovidgoyal.net>
 | 
			
		||||
 | 
			
		||||
from qt.core import QVBoxLayout
 | 
			
		||||
from qt.core import QAbstractItemView, QGroupBox, QHBoxLayout, QIcon, QLabel, Qt, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
 | 
			
		||||
 | 
			
		||||
from calibre.gui2.widgets2 import Dialog
 | 
			
		||||
from calibre.customize.ui import usbms_plugins
 | 
			
		||||
from calibre.gui2 import Application, choose_dir, gprefs
 | 
			
		||||
from calibre.gui2.widgets2 import Dialog, HistoryLineEdit2
 | 
			
		||||
from calibre.utils.icu import primary_sort_key
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ChooseFolder(QWidget):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        self.folder_edit = fe = HistoryLineEdit2(parent=self)
 | 
			
		||||
        fe.initialize('connect-to-folder')
 | 
			
		||||
        fe.setPlaceholderText(_('Path to folder to connect to'))
 | 
			
		||||
        if len(fe.history):
 | 
			
		||||
            fe.setText(fe.history[0])
 | 
			
		||||
        self.browse_button = bb = QToolButton(self)
 | 
			
		||||
        bb.setIcon(QIcon.ic('mimetypes/dir.png'))
 | 
			
		||||
        bb.clicked.connect(self.browse)
 | 
			
		||||
        self.l = l = QHBoxLayout(self)
 | 
			
		||||
        l.setContentsMargins(0, 0, 0, 0)
 | 
			
		||||
        l.addWidget(fe)
 | 
			
		||||
        l.addWidget(bb)
 | 
			
		||||
 | 
			
		||||
    def browse(self):
 | 
			
		||||
        ans = choose_dir(self, 'connect-to-folder-browse-history', _('Choose folder to connect to'))
 | 
			
		||||
        if ans:
 | 
			
		||||
            self.folder_edit.setText(ans)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def folder(self):
 | 
			
		||||
        return self.folder_edit.text().strip()
 | 
			
		||||
 | 
			
		||||
    @folder.setter
 | 
			
		||||
    def folder(self, val):
 | 
			
		||||
        self.folder_edit.setText((val or '').strip())
 | 
			
		||||
 | 
			
		||||
    def on_accept(self):
 | 
			
		||||
        if self.folder:
 | 
			
		||||
            self.folder_edit.save_history()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConnectToFolder(Dialog):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(_('Connect to folder'), 'connect-to-folderx', parent=parent)
 | 
			
		||||
        super().__init__(_('Connect to folder'), 'connect-to-folder', parent=parent)
 | 
			
		||||
 | 
			
		||||
    def setup_ui(self):
 | 
			
		||||
        self.l = l = QVBoxLayout(self)
 | 
			
		||||
        self.folder_chooser = fc = ChooseFolder(self)
 | 
			
		||||
        l.addWidget(fc)
 | 
			
		||||
        self.la = la = QLabel('<p>' + _(
 | 
			
		||||
            'Choose a device to connect as below. If no device is chosen a generic <i>Folder device</i>'
 | 
			
		||||
            ' will be used.'))
 | 
			
		||||
        self.la2 = la2 = QLabel('<p>' + _('<b>WARNING</b>: Connecting as a specific device will work only'
 | 
			
		||||
            ' if the chosen folder above contains the actual files from an actual device, as the'
 | 
			
		||||
            ' device drivers often expect to find certain device specific files. So only choose'
 | 
			
		||||
            ' a device below if you have copied the files from a real device or mounted it at the'
 | 
			
		||||
            ' chosen location.'))
 | 
			
		||||
        la.setWordWrap(True), la2.setWordWrap(True)
 | 
			
		||||
        l.addWidget(la)
 | 
			
		||||
        self.devices_group = dg = QGroupBox(_('Connect as device'), self)
 | 
			
		||||
        dg.setCheckable(True)
 | 
			
		||||
        l.addWidget(dg)
 | 
			
		||||
        l.addWidget(self.bb)
 | 
			
		||||
        dg.l = l = QVBoxLayout(dg)
 | 
			
		||||
        self.devices = d = QTreeWidget(self)
 | 
			
		||||
        d.setHeaderHidden(True)
 | 
			
		||||
        d.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
 | 
			
		||||
        l.addWidget(la2)
 | 
			
		||||
        l.addWidget(d)
 | 
			
		||||
 | 
			
		||||
        lcd = gprefs.get('last_connected_folder_as_device', None)
 | 
			
		||||
        selected_device = None
 | 
			
		||||
        man_map = {}
 | 
			
		||||
        for cls in usbms_plugins():
 | 
			
		||||
            for model in cls.model_metadata():
 | 
			
		||||
                man_map.setdefault(model.manufacturer_name, []).append(model)
 | 
			
		||||
        for manufacturer in sorted(man_map, key=primary_sort_key):
 | 
			
		||||
            devs = man_map[manufacturer]
 | 
			
		||||
            m = QTreeWidgetItem(d, 0)
 | 
			
		||||
            m.setText(0, manufacturer)
 | 
			
		||||
            m.setFlags(m.flags() & ~Qt.ItemFlag.ItemIsSelectable)
 | 
			
		||||
            flags = m.flags()
 | 
			
		||||
            devs.sort(key=lambda x: primary_sort_key(x.model_name))
 | 
			
		||||
            expanded = False
 | 
			
		||||
            for dev in devs:
 | 
			
		||||
                i = QTreeWidgetItem(m, 1)
 | 
			
		||||
                i.setText(0, dev.model_name)
 | 
			
		||||
                i.setData(0, Qt.ItemDataRole.UserRole, dev)
 | 
			
		||||
                i.setFlags(i.flags() | Qt.ItemFlag.ItemNeverHasChildren)
 | 
			
		||||
                if dev.settings_key == lcd:
 | 
			
		||||
                    i.setSelected(True)
 | 
			
		||||
                    expanded = True
 | 
			
		||||
                    selected_device = i
 | 
			
		||||
            m.setExpanded(expanded)
 | 
			
		||||
        if selected_device is not None:
 | 
			
		||||
            d.scrollToItem(selected_device)
 | 
			
		||||
        dg.setChecked(selected_device is not None)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def model_metadata(self):
 | 
			
		||||
        if self.devices_group.isChecked():
 | 
			
		||||
            for m in range(self.devices.topLevelItemCount()):
 | 
			
		||||
                man = self.devices.topLevelItem(m)
 | 
			
		||||
                for i in range(man.childCount()):
 | 
			
		||||
                    item = man.child(i)
 | 
			
		||||
                    if item.isSelected():
 | 
			
		||||
                        return item.data(0, Qt.ItemDataRole.UserRole)
 | 
			
		||||
 | 
			
		||||
    def accept(self):
 | 
			
		||||
        self.folder_chooser.on_accept()
 | 
			
		||||
        m = self.model_metadata
 | 
			
		||||
        gprefs.set('last_connected_folder_as_device', None if m is None else m.settings_key)
 | 
			
		||||
        return super().accept()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def ans(self):
 | 
			
		||||
        return self.folder_chooser.folder, self.model_metadata
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    app = Application([])
 | 
			
		||||
    d = ConnectToFolder()
 | 
			
		||||
    d.exec()
 | 
			
		||||
    print(d.ans)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user