widgets.patch: use DraggableTreeWidget
[git-cola.git] / cola / widgets / patch.py
blob7c59676c5919fe7d28f3ca349619f037f8937d48
1 import os
3 from PyQt4 import QtCore
4 from PyQt4 import QtGui
5 from PyQt4.QtCore import Qt
7 from cola import core
8 from cola import cmds
9 from cola import qtutils
10 from cola.i18n import N_
11 from cola.widgets import defs
12 from cola.widgets.standard import Dialog
13 from cola.widgets.standard import DraggableTreeWidget
16 def apply_patches():
17 parent = qtutils.active_window()
18 dlg = new_apply_patches(parent=parent)
19 dlg.show()
20 dlg.raise_()
21 return dlg
24 def new_apply_patches(patches=None, parent=None):
25 dlg = ApplyPatches(parent=parent)
26 if patches:
27 dlg.add_paths(patches)
28 return dlg
31 def get_patches_from_paths(paths):
32 paths = [core.decode(p) for p in paths]
33 patches = [p for p in paths
34 if core.isfile(p) and (
35 p.endswith('.patch') or p.endswith('.mbox'))]
36 dirs = [p for p in paths if core.isdir(p)]
37 dirs.sort()
38 for d in dirs:
39 patches.extend(get_patches_from_dir(d))
40 return patches
43 def get_patches_from_mimedata(mimedata):
44 urls = mimedata.urls()
45 if not urls:
46 return []
47 paths = map(lambda x: unicode(x.path()), urls)
48 return get_patches_from_paths(paths)
51 def get_patches_from_dir(path):
52 """Find patches in a subdirectory"""
53 patches = []
54 for root, subdirs, files in core.walk(path):
55 for name in [f for f in files if f.endswith('.patch')]:
56 patches.append(core.decode(os.path.join(root, name)))
57 return patches
60 class ApplyPatches(Dialog):
62 def __init__(self, parent=None):
63 super(ApplyPatches, self).__init__(parent=parent)
64 self.setAttribute(Qt.WA_MacMetalStyle)
65 self.setWindowTitle(N_('Apply Patches'))
66 self.setAcceptDrops(True)
67 if parent is not None:
68 self.setWindowModality(Qt.WindowModal)
70 self.curdir = os.getcwd()
71 self.inner_drag = False
73 self.usage = QtGui.QLabel()
74 self.usage.setText(N_("""
75 <p>
76 Drag and drop or use the <strong>Add</strong> button to add
77 patches to the list
78 </p>
79 """))
81 self.tree = PatchTreeWidget(parent=self)
82 self.tree.setHeaderHidden(True)
84 self.add_button = qtutils.create_toolbutton(
85 text=N_('Add'), icon=qtutils.add_icon(),
86 tooltip=N_('Add patches (+)'))
88 self.remove_button = qtutils.create_toolbutton(
89 text=N_('Remove'), icon=qtutils.remove_icon(),
90 tooltip=N_('Remove selected (Delete)'))
92 self.apply_button = qtutils.create_button(
93 text=N_('Apply'), icon=qtutils.apply_icon())
95 self.close_button = qtutils.create_button(
96 text=N_('Close'), icon=qtutils.close_icon())
98 self.add_action = qtutils.add_action(self,
99 N_('Add'), self.add_files,
100 Qt.Key_Plus)
102 self.remove_action = qtutils.add_action(self,
103 N_('Remove'), self.tree.remove_selected,
104 QtGui.QKeySequence.Delete, Qt.Key_Backspace,
105 Qt.Key_Minus)
107 layout = QtGui.QVBoxLayout()
108 layout.setMargin(defs.margin)
109 layout.setSpacing(defs.spacing)
111 top = QtGui.QHBoxLayout()
112 top.setMargin(defs.no_margin)
113 top.setSpacing(defs.button_spacing)
114 top.addWidget(self.add_button)
115 top.addWidget(self.remove_button)
116 top.addStretch()
117 top.addWidget(self.usage)
119 bottom = QtGui.QHBoxLayout()
120 bottom.setMargin(defs.no_margin)
121 bottom.setSpacing(defs.button_spacing)
122 bottom.addWidget(self.apply_button)
123 bottom.addStretch()
124 bottom.addWidget(self.close_button)
126 layout.addLayout(top)
127 layout.addWidget(self.tree)
128 layout.addLayout(bottom)
129 self.setLayout(layout)
131 qtutils.connect_button(self.add_button, self.add_files)
132 qtutils.connect_button(self.remove_button, self.tree.remove_selected)
133 qtutils.connect_button(self.apply_button, self.apply_patches)
134 qtutils.connect_button(self.close_button, self.close)
136 if not qtutils.apply_state(self):
137 self.resize(666, 420)
139 def apply_patches(self):
140 items = self.tree.items()
141 if not items:
142 return
143 patches = [unicode(i.data(0, Qt.UserRole).toPyObject()) for i in items]
144 cmds.do(cmds.ApplyPatches, patches)
145 self.accept()
147 def add_files(self):
148 files = qtutils.open_files(N_('Select patch file(s)...'),
149 directory=self.curdir,
150 filter='Patches (*.patch *.mbox)')
151 if not files:
152 return
153 files = [unicode(f) for f in files]
154 self.curdir = os.path.dirname(files[0])
155 self.add_paths([os.path.relpath(f) for f in files])
157 def dragEnterEvent(self, event):
158 """Accepts drops if the mimedata contains patches"""
159 super(ApplyPatches, self).dragEnterEvent(event)
160 patches = get_patches_from_mimedata(event.mimeData())
161 if patches:
162 event.acceptProposedAction()
164 def dropEvent(self, event):
165 """Add dropped patches"""
166 event.accept()
167 patches = get_patches_from_mimedata(event.mimeData())
168 if not patches:
169 return
170 self.add_paths(patches)
172 def add_paths(self, paths):
173 self.tree.add_paths(paths)
176 class PatchTreeWidget(DraggableTreeWidget):
178 def __init__(self, parent=None):
179 super(PatchTreeWidget, self).__init__(parent=parent)
181 def add_paths(self, paths):
182 patches = get_patches_from_paths(paths)
183 if not patches:
184 return
185 items = []
186 icon = qtutils.file_icon()
187 for patch in patches:
188 item = QtGui.QTreeWidgetItem()
189 flags = item.flags() & ~Qt.ItemIsDropEnabled
190 item.setFlags(flags)
191 item.setIcon(0, icon)
192 item.setText(0, os.path.basename(patch))
193 item.setData(0, Qt.UserRole, QtCore.QVariant(patch))
194 item.setToolTip(0, patch)
195 items.append(item)
196 self.addTopLevelItems(items)
198 def remove_selected(self):
199 idxs = self.selectedIndexes()
200 rows = [idx.row() for idx in idxs]
201 for row in reversed(sorted(rows)):
202 self.invisibleRootItem().takeChild(row)