|  10-24-2025, 11:41 AM | #1 | 
| Junior Member  Posts: 8 Karma: 10 Join Date: Oct 2025 Device: i dont have one | 
				
				help with developing a plugin for footnotes
			 
			
			hi , I'm new here and off course i need help i do not know how to even read python but i'm using AI and off course i'm stuck with a problem... I'm developing a large plugin for adding tool-tips, footnotes and sidebar notes and margin notes for a book i'm working on, and it is going to need a lot of notes So the problem i had at first was that the AI couldn't get the GUI buttons added to caliber editor and even when it succeeded it would break it by accident later and would take very long trying to fix it again . Eventually i decided to ask it to make a shim API for adding menus and buttons to the toolbar and it succeeded with the first but the second is broken and it cannot fix it Here is the code if any one can help make it work i will be super grateful it add menus and commands under them but it keep adding a drop-down menu to the toolbar and the AI can not tell how to make it add individual commands here is the python code for the shim Code: # shim.py
# Qt compatibility
try:
    from PyQt6.QtWidgets import (QMainWindow, QToolBar, QMenu, QToolButton)
    from PyQt6.QtCore import Qt
    from PyQt6.QtGui import QIcon, QAction
except ImportError:
    from PyQt5.QtWidgets import (QMainWindow, QToolBar, QMenu, QToolButton, QAction)
    from PyQt5.QtCore import Qt
    from PyQt5.QtGui import QIcon
# Mock Boss and GUI for testing
class MockGui(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(800, 600)
        self.setWindowTitle("Shim GUI Test")
        self.menuBar() # Initialize menu bar
        self.plugins_menu = self.menuBar().addMenu('&Plugins')
        tb = self.addToolBar("Plugins toolbar")
        tb.setObjectName("plugins-toolbar")
        tb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
class MockBoss:
    def __init__(self):
        self.gui = MockGui()
# ShimBoss with new APIs
class ShimBoss:
    def __init__(self, gui):
        self.gui = gui
    def get_plugins_menu(self):
        mb = self.gui.menuBar()
        for a in mb.actions():
            m = a.menu()
            if m and m.title() == '&Plugins':
                return m
        return None
    def add_menu_action(self, menu_path, action_text, callback, shortcut=None, icon_path=None, tooltip=None, name=None):
        paths = menu_path.split('|')
        if paths[0] == 'Plugins':
            menu = self.get_plugins_menu()
            paths = paths[1:]
        else:
            mb = self.gui.menuBar()
            root_title = paths[0]
            menu = None
            for a in mb.actions():
                m = a.menu()
                if m and m.title() == root_title:
                    menu = m
                    break
            if not menu:
                menu = mb.addMenu(root_title)
            paths = paths[1:]
        for p in paths:
            found = False
            for a in menu.actions():
                if a.menu() and a.menu().title() == p:
                    menu = a.menu()
                    found = True
                    break
            if not found:
                menu = menu.addMenu(p)
        ac = menu.addAction(action_text)
        if icon_path:
            ac.setIcon(QIcon(icon_path))
        if tooltip:
            ac.setToolTip(tooltip)
        if shortcut:
            ac.setShortcut(shortcut)
        if name:
            ac.setObjectName(name)
        ac.triggered.connect(callback)
        return ac
    def add_toolbar_action(self, action_text, callback, toolbar_name='plugins-toolbar', icon_path=None, tooltip=None, name=None):
        tb = self.gui.findChild(QToolBar, toolbar_name)
        if not tb:
            tb = self.gui.addToolBar('Plugins')
            tb.setObjectName(toolbar_name)
        ac = QAction(action_text, self.gui)
        if icon_path:
            ac.setIcon(QIcon(icon_path))
        if tooltip:
            ac.setToolTip(tooltip)
        if name:
            ac.setObjectName(name)
        ac.triggered.connect(callback)
        tb.addAction(ac)
        widget = tb.widgetForAction(ac)
        if widget:
            widget.setPopupMode(QToolButton.NoButton)
        return ac
    def add_toolbar_dropdown(self, button_text, actions_list, toolbar_name='plugins-toolbar', icon_path=None, tooltip=None):
        """
        Add a dropdown toolbar button with sub-actions.
        actions_list: list of dicts, e.g., [{'text': 'Sub1', 'callback': fn1, 'shortcut': None, 'icon_path': None, 'tooltip': None}]
        """
        tb = self.gui.findChild(QToolBar, toolbar_name)
        if not tb:
            tb = self.gui.addToolBar('Plugins')
            tb.setObjectName(toolbar_name)
        toolbutton = QToolButton(self.gui)
        toolbutton.setText(button_text)
        if icon_path:
            toolbutton.setIcon(QIcon(icon_path))
        if tooltip:
            toolbutton.setToolTip(tooltip)
        menu = QMenu(toolbutton)
        for act_dict in actions_list:
            ac = menu.addAction(act_dict['text'])
            if act_dict.get('icon_path'):
                ac.setIcon(QIcon(act_dict['icon_path']))
            if act_dict.get('tooltip'):
                ac.setToolTip(act_dict['tooltip'])
            if act_dict.get('shortcut'):
                ac.setShortcut(act_dict['shortcut'])
            ac.triggered.connect(act_dict['callback'])
        toolbutton.setMenu(menu)
        toolbutton.setPopupMode(QToolButton.InstantPopup)
        tb.addWidget(toolbutton)
        return toolbutton
    def hide_toolbar_action(self, name, toolbar_name='plugins-toolbar'):
        tb = self.gui.findChild(QToolBar, toolbar_name)
        if tb:
            for ac in tb.actions():
                if ac.objectName() == name:
                    ac.setVisible(False)
    def show_toolbar_action(self, name, toolbar_name='plugins-toolbar'):
        tb = self.gui.findChild(QToolBar, toolbar_name)
        if tb:
            for ac in tb.actions():
                if ac.objectName() == name:
                    ac.setVisible(True)
if __name__ == "__main__":
    try:
        from PyQt6.QtWidgets import QApplication
    except ImportError:
        from PyQt5.QtWidgets import QApplication
    import sys
    app = QApplication(sys.argv)
    boss = MockBoss()
    shim = ShimBoss(boss.gui)
    # Test additions
    def quick_fn():
        print("Quick action triggered")
    shim.add_menu_action('Plugins|Footnotes', 'Test Action', quick_fn, shortcut='Ctrl+T', tooltip='Test')
    shim.add_toolbar_action('Quick Action', quick_fn, tooltip='Quick fn', name='quick_action')
    # Example hide/show
    shim.hide_toolbar_action('quick_action')
    shim.show_toolbar_action('quick_action')
    boss.gui.show()
    sys.exit(app.exec_())even though this whole project was a test anyway to see if it will work and so far it is kind of is ... I have this output from the plugin and a semi working GUI Code: <span xml:lang="en" title="hello">hi4</span> <span xml:lang="en" title="hello" class="acronym">hi3</span> <span xml:lang="en" title="hello" class="glossary">hi2</span> <span xml:lang="en" title="hello" class="definition">hi</span> <sup><a href="balm.html#ftn3" id="bodyftn3" xml:lang="en" class="noteanchor">[3]</a></sup> <sup><a href="balm.html#ftn1" id="bodyftn1" xml:lang="en" class="noteanchor">[1]</a></sup> <sup><a href="balm.html#ftn2" id="bodyftn2" xml:lang="en" class="noteanchor">[2]</a></sup> <p class="footer">Footnotes</p><div id="ftn1" tabindex="0" xml:lang="en" class="note"><a href="balm.html#bodyftn1" class="noteSymbol">(1)</a> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">hello</p></div> <div id="ftn2" tabindex="0" xml:lang="en" class="sidebar"><a href="balm.html#bodyftn2" class="noteSymbol">(2)</a> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">hello</p></div> <aside id="ftn3" tabindex="0" xml:lang="en" class="marginnote"><a href="balm.html#bodyftn3" class="noteSymbol">(3)</a> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">hello</p></aside> frankly i have no real experience with digital book editing either and whither the compatibility of the output above with e-reader is any good. If anyone with experience is willing to advice with any improvement that will be wonderful. So anyway the AI kind of make me run around in circles but eventually it gets there. i'm not interested in someone giving me the full code although that would be welcome. just a hint about what is wrong or a code snippet. thank you in advance for your time and assistance | 
|   |   | 
|  10-24-2025, 03:48 PM | #2 | 
| Still reading            Posts: 14,990 Karma: 111111255 Join Date: Jun 2017 Location: Ireland Device: All 4 Kinds: epub eink, Kindle, android eink, NxtPaper | 
			
			Footnotes only work consistently with PDF or other Fixed Layout. Forget "AI" advice. | 
|   |   | 
| Advert | |
|  | 
|  10-24-2025, 06:26 PM | #3 | 
| null operator (he/him)            Posts: 22,012 Karma: 30277294 Join Date: Mar 2012 Location: Sydney Australia Device: none | 
			
			AFAIK the calibre ebook editor doesn't support user developed plugins, so I assume you're trying to use the Library Manager plugin framework.  To have the features you want (footnotes, tooltips, margin notes, sidebars etc) I would use a fixed-layout/desktop-publishing editor (e.g. Adobe InDesign, Quark) or a word-processor (Word, LO Writer, WordPerfect) as the starting point and publish the output as a PDF. BR Last edited by BetterRed; 10-25-2025 at 12:54 AM. | 
|   |   | 
|  10-24-2025, 10:08 PM | #4 | |
| Grand Sorcerer            Posts: 28,883 Karma: 207000000 Join Date: Jan 2010 Device: Nexus 7, Kindle Fire HD | Quote: 
 However... I have absolutely no feel for what the OP is asking for help with. It almost sounds like they're looking for a way to add menu items to Calibre's existing editor menus. Which I don't believe is possible (other than the plugin's own menu/submenus, of course). I certainly can't see why any plugin would be trying to add a menu item titled "Plugins" to its own gui. I also don't see a single call to any of Calibre's plugin API functions (editor or library). All I see is a standalone PyQt6 MainWindow program. If they're trying to write their own epub editor program from the ground up, calibre is not needed. Last edited by DiapDealer; 10-24-2025 at 10:14 PM. | |
|   |   | 
|  10-25-2025, 12:46 AM | #5 | 
| null operator (he/him)            Posts: 22,012 Karma: 30277294 Join Date: Mar 2012 Location: Sydney Australia Device: none | 
			
			Stand corrected, I've even used a couple, including your Toolbag:  I didn't find anything when I looked for doco under Editing e-books. I also looked for something in the Editor itself, to add and update them, but they get added via the Library Manager. So, I assumed I'd misremembered. It's the viewer that doesn't have them. Last edited by BetterRed; 10-25-2025 at 01:29 AM. | 
|   |   | 
| Advert | |
|  | 
|  10-25-2025, 02:06 AM | #6 | 
| Junior Member  Posts: 8 Karma: 10 Join Date: Oct 2025 Device: i dont have one | 
			
			The shim acts as an intermediary layer (or "shim") between the plugin's logic and the underlying Qt GUI framework. Its primary goals are: Compatibility Handling: It ensures the code works across different Qt versions (PyQt5 and PyQt6) by dynamically importing the appropriate modules and using version-agnostic APIs. Abstraction of GUI Operations: It simplifies common tasks like adding menu items, toolbar buttons, and dropdowns, without requiring direct manipulation of Qt objects (e.g., QMenu, QToolBar, QAction). This makes the code more maintainable and testable. Plugin Integration: In applications like Calibre, plugins often need to integrate into the main GUI (e.g., adding items to menus like '&Plugins' or toolbars). The shim provides a standardized way to do this, handling edge cases such as creating submenus or finding existing UI elements. Testing Support: It includes mock classes (MockGui and MockBoss) to simulate the GUI environment for standalone testing, allowing the code to run outside the full application. Without the shim, the plugin code would need to directly interact with the application's gui object (e.g., boss.gui.menuBar()), which could lead to version-specific issues or tighter coupling to the host application. Key Components and Methods of the Shim The ShimBoss class is initialized with a gui object (typically from the application's boss.gui). It exposes several methods for GUI modifications: get_plugins_menu(): Searches the main menu bar for an existing '&Plugins' menu. Returns the menu if found; otherwise, returns None. This is used as a starting point for plugin-specific menu additions, ensuring they integrate into a standard location. add_menu_action(menu_path, action_text, callback, shortcut=None, icon_path=None, tooltip=None, name=None): Adds a new action (menu item) to a specified menu path. The menu_path is a pipe-separated string (e.g., 'Plugins|Footnotes' or 'Tools|My Plugin'), which the shim parses to create or navigate submenus dynamically. If the path starts with 'Plugins', it uses the existing '&Plugins' menu as the root. Configures the action with optional shortcut, icon, tooltip, and object name. Connects the action's triggered signal to the provided callback function. Returns the created QAction object. Example from shim.py: shim.add_menu_action('Plugins|Footnotes', 'Test Action', quick_fn, shortcut='Ctrl+T', tooltip='Test'). add_toolbar_action(action_text, callback, toolbar_name='plugins-toolbar', icon_path=None, tooltip=None, name=None): Adds a button (action) to a specified toolbar (defaults to 'plugins-toolbar'). If the toolbar doesn't exist, creates it. Configures the action with optional icon, tooltip, and name. Connects the action to the callback. Ensures the button doesn't have a popup mode (flat button). Returns the created QAction. Example from shim.py: shim.add_toolbar_action('Quick Action', quick_fn, tooltip='Quick fn', name='quick_action'). add_toolbar_dropdown(button_text, actions_list, toolbar_name='plugins-toolbar', icon_path=None, tooltip=None): Adds a dropdown button to the toolbar. actions_list is a list of dictionaries, each defining a sub-action (e.g., {'text': 'Sub1', 'callback': fn1}). Creates a QToolButton with a QMenu for the dropdown. Sets popup mode to instant (menu appears on click). Returns the QToolButton. This allows grouping related actions under one button for better UI organization. hide_toolbar_action(name, toolbar_name='plugins-toolbar') and show_toolbar_action(name, toolbar_name='plugins-toolbar'): Hides or shows a toolbar action by its object name. Searches the toolbar for matching actions and toggles their visibility. Useful for dynamic UI control (e.g., enabling/disabling features based on context). Example from shim.py: shim.hide_toolbar_action('quick_action') followed by shim.show_toolbar_action('quick_action'). | 
|   |   | 
|  10-25-2025, 03:55 AM | #7 | |
| Junior Member  Posts: 8 Karma: 10 Join Date: Oct 2025 Device: i dont have one | Quote: 
 That was actually helpful. the AI simply made this code to work with pyqt without respecting the editor constrains and I did not know. so that fixed a bug, still the bug of creating a normal shortcut in the toolbar using the shim is not solved. but for now I'm using the menu until I figure something, I may revert to normal pyqt if I can't, but hopefully I will. I prefer to find away to make this easier in case i need to develop other plugins in the future , i really hate GUI development | |
|   |   | 
|  10-25-2025, 06:22 AM | #8 | ||
| Still reading            Posts: 14,990 Karma: 111111255 Join Date: Jun 2017 Location: Ireland Device: All 4 Kinds: epub eink, Kindle, android eink, NxtPaper | Quote: 
 Quote: 
 | ||
|   |   | 
|  10-25-2025, 06:55 AM | #9 | 
| Junior Member  Posts: 8 Karma: 10 Join Date: Oct 2025 Device: i dont have one | 
			
			the idea of doing developing this is out on necessity to me because epub is essentially an HTML format masquerading as something else..  So it that can be easily converted with minimum effort into a static website or a web app, aside from that despite all of the caliber editor flaws it is the only option for the work i do with huge amount of html files and a very good search and replace engine with regex .. so I need to use this. It is why I do not use sigil managing saved search and replace presets is very difficult for fast workflow i have almost hundreds of search and replace regex sets and finding what i need in sigil is a nightmare pdf simply dos not support the feature set i need | 
|   |   | 
|  10-25-2025, 08:57 AM | #10 | 
| Grand Sorcerer            Posts: 28,883 Karma: 207000000 Join Date: Jan 2010 Device: Nexus 7, Kindle Fire HD | 
			
			You would not be the first person managing hundreds of Sigil search and replace sets. That's why you can group them and string them together in managable automation lists. You also are not the first person to believe your workflow is too special to be accommodated with the built in tools provided by Calibre and/or Sigil. That typically only means that you simply haven't taken the time to familiarize yourself with all of their various features. But regardless of all that. The code you posted does not include a single calibre plugin API function. Until it does, it's not a calibre plugin. It's just code you're trying to make meddle with Calibre's menus. You don't need shims or boss classes to add your plugin's icon/menus/submenus to Calibre's editor toolbar. The Calibre API has built-in functionality to accommodate just that without tricks. Calibre also provides compatibility code so you don't have to worry about Qt5 vs Qt6. You're trying to reinvent the entire wheel when all you're missing is a lug nut or two. I wish you the best of luck, but you really should try to familiarize yourself with Calibre's plugin framework before trying to create one. It would save you a ton of trouble. | 
|   |   | 
|  10-25-2025, 10:58 AM | #11 | |
| Junior Member  Posts: 8 Karma: 10 Join Date: Oct 2025 Device: i dont have one | Quote: 
  but i said fast work flow is not possible specifically in Sigil not Calibre editor I did not say my work is special I implied that my distribution objective and format is specific that is all . aside from that: I'm translating the bible to Arabic from hebrew with strong number system and footnote and historical notes and other additions. I have been at this work for the last 3.5 years for almost 14 hours a day almost every day. because it is just me  So any time saved to focus on my main objective is better than fighting with the tools I Use . Having what feels like a giant context menu (like what comes out when selecting in Sigil) that block the screen just to select a regex, is very off putting and distracting to me at least. I prefer Calibre editor side panel that is all i said. I'm not sure why did that cause offense. Beside I already built the regex collection in editor even if there is a way to use Sigil that i did not discover, I'm unwilling to spend the time to migrate now. In any case I'm trying to build tools that will help me build more plugins easily in the future not just this plugin. to expand editor for my use, so there is that I already did start on a large shim that gives Calibre the same API as Sigil to help migrate any plugin I need from Sigil to Calibre editor . it is fished but still need to test it and will publish it as will ... the shim for the GUI is specifically for making it easy of AI to work with GUI since they do not seem to be good at it for some reason. they keep changing GUI code randomly and breaking it every time. so a less complex way of adding to the menu seemed like a good idea at the time ...   | |
|   |   | 
|  10-26-2025, 01:14 AM | #12 | 
| Junior Member  Posts: 8 Karma: 10 Join Date: Oct 2025 Device: i dont have one | 
			
			figured what was causing the bug and it is not the shim .  it is working correctly but the code the AI built is one large class with multiple action but calibre dos not support individual command in the toolbar form one class I had to refactor the code into multiple classes with 1 action per class and now every thing work and the plugin is coming up nicely maybe in 2 or 3 days i will have something for people to test if any one interested .... | 
|   |   | 
|  10-26-2025, 07:28 AM | #13 | 
| Still reading            Posts: 14,990 Karma: 111111255 Join Date: Jun 2017 Location: Ireland Device: All 4 Kinds: epub eink, Kindle, android eink, NxtPaper | 
			
			Don't have AI do anything.
		 | 
|   |   | 
|  | 
| 
 | 
|  Similar Threads | ||||
| Thread | Thread Starter | Forum | Replies | Last Post | 
| When developing a plugin, how can I invoke the task? | jsky | Development | 1 | 10-22-2025 03:22 AM | 
| How to debug during developing a sigil plugin? | nir34 | Sigil | 0 | 11-09-2020 08:58 PM | 
| Help developing kepubify plugin for calibre | geek1011 | Development | 14 | 11-26-2018 09:35 PM | 
| Developing plugin to process notes from Sony PRS-T1: no module named | sjvs | Development | 19 | 03-25-2012 11:53 AM | 
| Developing a plugin on osx 10.5 | macuser15905 | Plugins | 2 | 08-28-2009 03:54 PM |