ソースを参照

Load ~/.labelmerc for user defined shortcuts

Kentaro Wada 7 年 前
コミット
571f8dabba
4 ファイル変更130 行追加47 行削除
  1. 95 46
      labelme/app.py
  2. 9 0
      labelme/config/__init__.py
  3. 25 0
      labelme/config/default_config.yaml
  4. 1 1
      setup.py

+ 95 - 46
labelme/app.py

@@ -3,17 +3,20 @@ import functools
 import os.path
 import os.path
 import subprocess
 import subprocess
 import sys
 import sys
+import warnings
 
 
 from qtpy import QT_VERSION
 from qtpy import QT_VERSION
 from qtpy import QtCore
 from qtpy import QtCore
 from qtpy.QtCore import Qt
 from qtpy.QtCore import Qt
 from qtpy import QtGui
 from qtpy import QtGui
 from qtpy import QtWidgets
 from qtpy import QtWidgets
+import yaml
 
 
 QT5 = QT_VERSION[0] == '5'
 QT5 = QT_VERSION[0] == '5'
 
 
 from labelme.canvas import Canvas
 from labelme.canvas import Canvas
 from labelme.colorDialog import ColorDialog
 from labelme.colorDialog import ColorDialog
+from labelme.config import default_config
 from labelme.labelDialog import LabelDialog
 from labelme.labelDialog import LabelDialog
 from labelme.labelFile import LabelFile
 from labelme.labelFile import LabelFile
 from labelme.labelFile import LabelFileError
 from labelme.labelFile import LabelFileError
@@ -219,44 +222,55 @@ class MainWindow(QtWidgets.QMainWindow, WindowMixin):
                              QtWidgets.QDockWidget.DockWidgetFloatable)
                              QtWidgets.QDockWidget.DockWidgetFloatable)
         self.dock.setFeatures(self.dock.features() ^ self.dockFeatures)
         self.dock.setFeatures(self.dock.features() ^ self.dockFeatures)
 
 
+        config = self.getConfig()
+
         # Actions
         # Actions
         action = functools.partial(newAction, self)
         action = functools.partial(newAction, self)
-        quit = action('&Quit', self.close, 'Ctrl+Q', 'quit',
+        shortcuts = config['shortcuts']
+        quit = action('&Quit', self.close, shortcuts['quit'], 'quit',
                       'Quit application')
                       'Quit application')
-        open_ = action('&Open', self.openFile, 'Ctrl+O', 'open',
+        open_ = action('&Open', self.openFile, shortcuts['open'], 'open',
                        'Open image or label file')
                        'Open image or label file')
-        opendir = action('&Open Dir', self.openDirDialog, 'Ctrl+U', 'open',
-                         u'Open Dir')
-        openNextImg = action('&Next Image', self.openNextImg, 'D', 'next',
-                             u'Open Next')
-
-        openPrevImg = action('&Prev Image', self.openPrevImg, 'A', 'prev',
-                             u'Open Prev')
-        save = action('&Save', self.saveFile, 'Ctrl+S', 'save',
+        opendir = action('&Open Dir', self.openDirDialog,
+                         shortcuts['open_dir'], 'open', u'Open Dir')
+        openNextImg = action('&Next Image', self.openNextImg,
+                             shortcuts['open_next'], 'next', u'Open Next')
+
+        openPrevImg = action('&Prev Image', self.openPrevImg,
+                             shortcuts['open_prev'], 'prev', u'Open Prev')
+        save = action('&Save', self.saveFile, shortcuts['save'], 'save',
                       'Save labels to file', enabled=False)
                       'Save labels to file', enabled=False)
-        saveAs = action('&Save As', self.saveFileAs, 'Ctrl+Shift+S', 'save-as',
-                        'Save labels to a different file', enabled=False)
-        close = action('&Close', self.closeFile, 'Ctrl+W', 'close',
+        saveAs = action('&Save As', self.saveFileAs, shortcuts['save_as'],
+                        'save-as', 'Save labels to a different file',
+                        enabled=False)
+        close = action('&Close', self.closeFile, shortcuts['close'], 'close',
                        'Close current file')
                        'Close current file')
-        color1 = action('Polygon &Line Color', self.chooseColor1, 'Ctrl+L',
-                        'color_line', 'Choose polygon line color')
+        color1 = action('Polygon &Line Color', self.chooseColor1,
+                        shortcuts['edit_line_color'], 'color_line',
+                        'Choose polygon line color')
         color2 = action('Polygon &Fill Color', self.chooseColor2,
         color2 = action('Polygon &Fill Color', self.chooseColor2,
-                        'Ctrl+Shift+L', 'color', 'Choose polygon fill color')
-
-        createMode = action('Create\nPolygo&ns', self.setCreateMode, 'Ctrl+N',
-                            'objects', 'Start drawing polygons', enabled=False)
-        editMode = action('&Edit\nPolygons', self.setEditMode, 'Ctrl+J',
-                          'edit', 'Move and edit polygons', enabled=False)
-
-        create = action('Create\nPolygo&n', self.createShape, 'Ctrl+N',
-                        'objects', 'Draw a new polygon', enabled=False)
-        delete = action('Delete\nPolygon', self.deleteSelectedShape, 'Delete',
-                        'cancel', 'Delete', enabled=False)
-        copy = action('&Duplicate\nPolygon', self.copySelectedShape, 'Ctrl+D',
-                      'copy', 'Create a duplicate of the selected polygon',
+                        shortcuts['edit_fill_color'], 'color',
+                        'Choose polygon fill color')
+
+        createMode = action('Create\nPolygo&ns', self.setCreateMode,
+                            shortcuts['create_polygon'], 'objects',
+                            'Start drawing polygons', enabled=False)
+        editMode = action('&Edit\nPolygons', self.setEditMode,
+                          shortcuts['edit_polygon'], 'edit',
+                          'Move and edit polygons', enabled=False)
+
+        create = action('Create\nPolygo&n', self.createShape,
+                        shortcuts['create_polygon'], 'objects',
+                        'Draw a new polygon', enabled=False)
+        delete = action('Delete\nPolygon', self.deleteSelectedShape,
+                        shortcuts['delete_polygon'], 'cancel',
+                        'Delete', enabled=False)
+        copy = action('&Duplicate\nPolygon', self.copySelectedShape,
+                      shortcuts['duplicate_polygon'], 'copy',
+                      'Create a duplicate of the selected polygon',
                       enabled=False)
                       enabled=False)
         undoLastPoint = action('Undo last point', self.canvas.undoLastPoint,
         undoLastPoint = action('Undo last point', self.canvas.undoLastPoint,
-                               ['Ctrl+Z', 'Backspace'], 'undoLastPoint',
+                               shortcuts['undo_last_point'], 'undoLastPoint',
                                'Undo last drawn point', enabled=False)
                                'Undo last drawn point', enabled=False)
 
 
         advancedMode = action('&Advanced Mode', self.toggleAdvancedMode,
         advancedMode = action('&Advanced Mode', self.toggleAdvancedMode,
@@ -265,37 +279,41 @@ class MainWindow(QtWidgets.QMainWindow, WindowMixin):
 
 
         hideAll = action('&Hide\nPolygons',
         hideAll = action('&Hide\nPolygons',
                          functools.partial(self.togglePolygons, False),
                          functools.partial(self.togglePolygons, False),
-                         'Ctrl+H', 'eye', 'Hide all polygons', enabled=False)
+                         icon='eye', tip='Hide all polygons', enabled=False)
         showAll = action('&Show\nPolygons',
         showAll = action('&Show\nPolygons',
                          functools.partial(self.togglePolygons, True),
                          functools.partial(self.togglePolygons, True),
-                         'Ctrl+A', 'eye', 'Show all polygons', enabled=False)
+                         icon='eye', tip='Show all polygons', enabled=False)
 
 
-        help = action('&Tutorial', self.tutorial, 'Ctrl+T', 'help',
-                      'Show screencast of introductory tutorial')
+        help = action('&Tutorial', self.tutorial, icon='help',
+                      tip='Show screencast of introductory tutorial')
 
 
         zoom = QtWidgets.QWidgetAction(self)
         zoom = QtWidgets.QWidgetAction(self)
         zoom.setDefaultWidget(self.zoomWidget)
         zoom.setDefaultWidget(self.zoomWidget)
         self.zoomWidget.setWhatsThis(
         self.zoomWidget.setWhatsThis(
             "Zoom in or out of the image. Also accessible with"
             "Zoom in or out of the image. Also accessible with"
             " %s and %s from the canvas." %
             " %s and %s from the canvas." %
-            (fmtShortcut("Ctrl+[-+]"), fmtShortcut("Ctrl+Wheel")))
+            (fmtShortcut('%s,%s' % (shortcuts['zoom_in'],
+                                    shortcuts['zoom_out'])),
+             fmtShortcut("Ctrl+Wheel")))
         self.zoomWidget.setEnabled(False)
         self.zoomWidget.setEnabled(False)
 
 
         zoomIn = action('Zoom &In', functools.partial(self.addZoom, 10),
         zoomIn = action('Zoom &In', functools.partial(self.addZoom, 10),
-                        'Ctrl++', 'zoom-in',
+                        shortcuts['zoom_in'], 'zoom-in',
                         'Increase zoom level', enabled=False)
                         'Increase zoom level', enabled=False)
         zoomOut = action('&Zoom Out', functools.partial(self.addZoom, -10),
         zoomOut = action('&Zoom Out', functools.partial(self.addZoom, -10),
-                         'Ctrl+-', 'zoom-out',
+                         shortcuts['zoom_out'], 'zoom-out',
                          'Decrease zoom level', enabled=False)
                          'Decrease zoom level', enabled=False)
         zoomOrg = action('&Original size',
         zoomOrg = action('&Original size',
                          functools.partial(self.setZoom, 100),
                          functools.partial(self.setZoom, 100),
-                         'Ctrl+0', 'zoom', 'Zoom to original size',
-                         enabled=False)
-        fitWindow = action('&Fit Window', self.setFitWindow, 'Ctrl+F',
-                           'fit-window', 'Zoom follows window size',
-                           checkable=True, enabled=False)
-        fitWidth = action('Fit &Width', self.setFitWidth, 'Ctrl+Shift+F',
-                          'fit-width', 'Zoom follows window width',
+                         shortcuts['zoom_to_original'], 'zoom',
+                         'Zoom to original size', enabled=False)
+        fitWindow = action('&Fit Window', self.setFitWindow,
+                           shortcuts['fit_window'], 'fit-window',
+                           'Zoom follows window size', checkable=True,
+                           enabled=False)
+        fitWidth = action('Fit &Width', self.setFitWidth,
+                          shortcuts['fit_width'], 'fit-width',
+                          'Zoom follows window width',
                           checkable=True, enabled=False)
                           checkable=True, enabled=False)
         # Group zoom controls into a list for easier toggling.
         # Group zoom controls into a list for easier toggling.
         zoomActions = (self.zoomWidget, zoomIn, zoomOut, zoomOrg,
         zoomActions = (self.zoomWidget, zoomIn, zoomOut, zoomOrg,
@@ -308,8 +326,8 @@ class MainWindow(QtWidgets.QMainWindow, WindowMixin):
             self.MANUAL_ZOOM: lambda: 1,
             self.MANUAL_ZOOM: lambda: 1,
         }
         }
 
 
-        edit = action('&Edit Label', self.editLabel, 'Ctrl+E', 'edit',
-                      'Modify the label of the selected polygon',
+        edit = action('&Edit Label', self.editLabel, shortcuts['edit_label'],
+                      'edit', 'Modify the label of the selected polygon',
                       enabled=False)
                       enabled=False)
         self.editButton.setDefaultAction(edit)
         self.editButton.setDefaultAction(edit)
 
 
@@ -322,7 +340,6 @@ class MainWindow(QtWidgets.QMainWindow, WindowMixin):
 
 
         labels = self.dock.toggleViewAction()
         labels = self.dock.toggleViewAction()
         labels.setText('Show/Hide Label Panel')
         labels.setText('Show/Hide Label Panel')
-        labels.setShortcut('Ctrl+Shift+L')
 
 
         # Lavel list context menu.
         # Lavel list context menu.
         labelMenu = QtWidgets.QMenu()
         labelMenu = QtWidgets.QMenu()
@@ -457,6 +474,38 @@ class MainWindow(QtWidgets.QMainWindow, WindowMixin):
 
 
     # Support Functions
     # Support Functions
 
 
+    def getConfig(self):
+        # shortcuts for actions
+        home = os.path.expanduser('~')
+        config_file = os.path.join(home, '.labelmerc')
+
+        # default config
+        config = default_config.copy()
+
+        def update_dict(target_dict, new_dict):
+            for key, value in new_dict.items():
+                if key not in target_dict:
+                    print('Skipping unexpected key in config: {}'.format(key))
+                    continue
+                if isinstance(target_dict[key], dict) and \
+                        isinstance(value, dict):
+                    update_dict(target_dict[key], value)
+                else:
+                    target_dict[key] = value
+
+        if os.path.exists(config_file):
+            user_config = yaml.load(open(config_file)) or {}
+            update_dict(config, user_config)
+
+        # save config
+        try:
+            yaml.safe_dump(config, open(config_file, 'w'),
+                           default_flow_style=False)
+        except Exception:
+            warnings.warn('Failed to save config: {}'.format(config_file))
+
+        return config
+
     def noShapes(self):
     def noShapes(self):
         return not self.labelList.itemsToShapes
         return not self.labelList.itemsToShapes
 
 

+ 9 - 0
labelme/config/__init__.py

@@ -0,0 +1,9 @@
+import os.path as osp
+
+import yaml
+
+
+here = osp.dirname(osp.abspath(__file__))
+config_file = osp.join(here, 'default_config.yaml')
+default_config = yaml.load(open(config_file))
+del here, config_file

+ 25 - 0
labelme/config/default_config.yaml

@@ -0,0 +1,25 @@
+shortcuts:
+  close: Ctrl+W
+  open: Ctrl+O
+  open_dir: Ctrl+U
+  quit: Ctrl+Q
+  save: Ctrl+S
+  save_as: Ctrl+Shift+S
+
+  open_next: D
+  open_prev: A
+
+  zoom_in: Ctrl++
+  zoom_out: Ctrl+-
+  zoom_to_original: Ctrl+0
+  fit_window: Ctrl+F
+  fit_width: Ctrl+Shift+F
+
+  create_polygon: Ctrl+N
+  edit_polygon: Ctrl+J
+  delete_polygon: Delete
+  duplicate_polygon: Ctrl+D
+  undo_last_point: [Ctrl+Z, Backspace]
+  edit_label: Ctrl+E
+  edit_line_color: Ctrl+L
+  edit_fill_color: Ctrl+Shift+L

+ 1 - 1
setup.py

@@ -86,7 +86,7 @@ setup(
         'Operating System :: POSIX',
         'Operating System :: POSIX',
         'Topic :: Internet :: WWW/HTTP',
         'Topic :: Internet :: WWW/HTTP',
     ],
     ],
-    package_data={'labelme': ['icons/*']},
+    package_data={'labelme': ['icons/*', 'config/*.yaml']},
     entry_points={
     entry_points={
         'console_scripts': [
         'console_scripts': [
             'labelme=labelme.app:main',
             'labelme=labelme.app:main',