More work on the connect to folder dialog

This commit is contained in:
Kovid Goyal 2025-03-05 15:47:07 +05:30
parent c4de6b9bd9
commit 9684457744
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
11 changed files with 147 additions and 17 deletions

View File

@ -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

View File

@ -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',

View File

@ -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'

View File

@ -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:

View File

@ -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]

View File

@ -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']

View File

@ -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 = [

View File

@ -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'

View File

@ -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']

View File

@ -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:

View File

@ -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)