/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to commit.py

  • Committer: Jelmer Vernooij
  • Date: 2007-07-15 15:05:06 UTC
  • Revision ID: jelmer@samba.org-20070715150506-1uemecr5kt7d4kvg
Fix whitespace, add comment.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
import sys
18
 
 
19
17
try:
20
18
    import pygtk
21
19
    pygtk.require("2.0")
22
20
except:
23
21
    pass
24
 
try:
25
 
    import gtk
26
 
    import gtk.glade
27
 
    import gobject
28
 
    import pango
29
 
except:
30
 
    sys.exit(1)
31
 
 
32
 
from bzrlib import version_info
33
 
 
34
 
if version_info < (0, 9):
35
 
    # function deprecated after 0.9
36
 
    from bzrlib.delta import compare_trees
 
22
 
 
23
import gtk
 
24
import gobject
 
25
import pango
 
26
 
 
27
import os.path
37
28
 
38
29
import bzrlib.errors as errors
39
 
from bzrlib.workingtree import WorkingTree
40
 
 
41
 
class OliveCommit:
42
 
    """ Display Commit dialog and perform the needed actions. """
43
 
    def __init__(self, gladefile, comm, dialog):
44
 
        """ Initialize the Commit dialog. """
45
 
        self.gladefile = gladefile
46
 
        self.glade = gtk.glade.XML(self.gladefile, 'window_commit', 'olive-gtk')
47
 
        
48
 
        # Communication object
49
 
        self.comm = comm
50
 
        # Dialog object
51
 
        self.dialog = dialog
52
 
        
53
 
        # Get some important widgets
54
 
        self.window = self.glade.get_widget('window_commit')
55
 
        self.checkbutton_local = self.glade.get_widget('checkbutton_commit_local')
56
 
        self.textview = self.glade.get_widget('textview_commit')
57
 
        self.file_view = self.glade.get_widget('treeview_commit_select')
58
 
 
59
 
        # Check if current location is a branch
60
 
        try:
61
 
            (self.wt, path) = WorkingTree.open_containing(self.comm.get_path())
62
 
            branch = self.wt.branch
63
 
        except errors.NotBranchError:
64
 
            self.notbranch = True
65
 
            return
66
 
        except:
67
 
            raise
68
 
 
69
 
        file_id = self.wt.path2id(path)
70
 
 
71
 
        self.notbranch = False
72
 
        if file_id is None:
73
 
            self.notbranch = True
74
 
            return
 
30
from bzrlib import osutils
 
31
 
 
32
from dialog import error_dialog, question_dialog
 
33
from errors import show_bzr_error
 
34
 
 
35
try:
 
36
    import dbus
 
37
    import dbus.glib
 
38
    have_dbus = True
 
39
except ImportError:
 
40
    have_dbus = False
 
41
 
 
42
class CommitDialog(gtk.Dialog):
 
43
    """ New implementation of the Commit dialog. """
 
44
    def __init__(self, wt, wtpath, notbranch, selected=None, parent=None):
 
45
        """ Initialize the Commit Dialog. """
 
46
        gtk.Dialog.__init__(self, title="Commit - Olive",
 
47
                                  parent=parent,
 
48
                                  flags=0,
 
49
                                  buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
 
50
        
 
51
        # Get arguments
 
52
        self.wt = wt
 
53
        self.wtpath = wtpath
 
54
        self.notbranch = notbranch
 
55
        self.selected = selected
75
56
        
76
57
        # Set the delta
77
58
        self.old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
78
 
        if version_info < (0, 9):
79
 
            self.delta = compare_trees(self.old_tree, self.wt)
80
 
        else:
81
 
            self.delta = self.wt.changes_from(self.old_tree)
82
 
        
83
 
        # Dictionary for signal_autoconnect
84
 
        dic = { "on_button_commit_commit_clicked": self.commit,
85
 
                "on_button_commit_cancel_clicked": self.close }
86
 
        
87
 
        # Connect the signals to the handlers
88
 
        self.glade.signal_autoconnect(dic)
 
59
        self.delta = self.wt.changes_from(self.old_tree)
 
60
        
 
61
        # Get pending merges
 
62
        self.pending = self._pending_merges(self.wt)
 
63
        
 
64
        # Do some preliminary checks
 
65
        self._is_checkout = False
 
66
        self._is_pending = False
 
67
        if self.wt is None and not self.notbranch:
 
68
            error_dialog(_('Directory does not have a working tree'),
 
69
                         _('Operation aborted.'))
 
70
            self.close()
 
71
            return
 
72
 
 
73
        if self.notbranch:
 
74
            error_dialog(_('Directory is not a branch'),
 
75
                         _('You can perform this action only in a branch.'))
 
76
            self.close()
 
77
            return
 
78
        else:
 
79
            if self.wt.branch.get_bound_location() is not None:
 
80
                # we have a checkout, so the local commit checkbox must appear
 
81
                self._is_checkout = True
 
82
            
 
83
            if self.pending:
 
84
                # There are pending merges, file selection not supported
 
85
                self._is_pending = True
 
86
        
 
87
        # Create the widgets
 
88
        self._button_commit = gtk.Button(_("Comm_it"), use_underline=True)
 
89
        self._expander_files = gtk.Expander(_("File(s) to commit"))
 
90
        self._vpaned_main = gtk.VPaned()
 
91
        self._scrolledwindow_files = gtk.ScrolledWindow()
 
92
        self._scrolledwindow_message = gtk.ScrolledWindow()
 
93
        self._treeview_files = gtk.TreeView()
 
94
        self._vbox_message = gtk.VBox()
 
95
        self._label_message = gtk.Label(_("Commit message:"))
 
96
        self._textview_message = gtk.TextView()
 
97
        
 
98
        if self._is_pending:
 
99
            self._expander_merges = gtk.Expander(_("Pending merges"))
 
100
            self._vpaned_list = gtk.VPaned()
 
101
            self._scrolledwindow_merges = gtk.ScrolledWindow()
 
102
            self._treeview_merges = gtk.TreeView()
 
103
 
 
104
        # Set callbacks
 
105
        self._button_commit.connect('clicked', self._on_commit_clicked)
 
106
        self._treeview_files.connect('row_activated', self._on_treeview_files_row_activated)
 
107
        
 
108
        # Set properties
 
109
        self._scrolledwindow_files.set_policy(gtk.POLICY_AUTOMATIC,
 
110
                                              gtk.POLICY_AUTOMATIC)
 
111
        self._scrolledwindow_message.set_policy(gtk.POLICY_AUTOMATIC,
 
112
                                                gtk.POLICY_AUTOMATIC)
 
113
        self._textview_message.modify_font(pango.FontDescription("Monospace"))
 
114
        self.set_default_size(500, 500)
 
115
        self._vpaned_main.set_position(200)
 
116
        self._button_commit.set_flags(gtk.CAN_DEFAULT)
 
117
 
 
118
        if self._is_pending:
 
119
            self._scrolledwindow_merges.set_policy(gtk.POLICY_AUTOMATIC,
 
120
                                                   gtk.POLICY_AUTOMATIC)
 
121
            self._treeview_files.set_sensitive(False)
 
122
        
 
123
        # Construct the dialog
 
124
        self.action_area.pack_end(self._button_commit)
 
125
        
 
126
        self._scrolledwindow_files.add(self._treeview_files)
 
127
        self._scrolledwindow_message.add(self._textview_message)
 
128
        
 
129
        self._expander_files.add(self._scrolledwindow_files)
 
130
        
 
131
        self._vbox_message.pack_start(self._label_message, False, False)
 
132
        self._vbox_message.pack_start(self._scrolledwindow_message, True, True)
 
133
        
 
134
        if self._is_pending:        
 
135
            self._expander_merges.add(self._scrolledwindow_merges)
 
136
            self._scrolledwindow_merges.add(self._treeview_merges)
 
137
            self._vpaned_list.add1(self._expander_files)
 
138
            self._vpaned_list.add2(self._expander_merges)
 
139
            self._vpaned_main.add1(self._vpaned_list)
 
140
        else:
 
141
            self._vpaned_main.add1(self._expander_files)
 
142
 
 
143
        self._vpaned_main.add2(self._vbox_message)
 
144
        
 
145
        self.vbox.pack_start(self._vpaned_main, True, True)
 
146
        if self._is_checkout: 
 
147
            self._check_local = gtk.CheckButton(_("_Only commit locally"),
 
148
                                                use_underline=True)
 
149
            self.vbox.pack_start(self._check_local, False, False)
 
150
            if have_dbus:
 
151
                bus = dbus.SystemBus()
 
152
                proxy_obj = bus.get_object('org.freedesktop.NetworkManager', 
 
153
                              '/org/freedesktop/NetworkManager')
 
154
                dbus_iface = dbus.Interface(
 
155
                        proxy_obj, 'org.freedesktop.NetworkManager')
 
156
                # 3 is the enum value for STATE_CONNECTED
 
157
                self._check_local.set_active(dbus_iface.state() != 3)
89
158
        
90
159
        # Create the file list
91
160
        self._create_file_view()
92
 
    
93
 
    def display(self):
94
 
        """ Display the Push dialog. """
95
 
        if self.notbranch:
96
 
            self.dialog.error_dialog(_('Directory is not a branch'),
97
 
                                     _('You can perform this action only in a branch.'))
98
 
            self.close()
99
 
        else:
100
 
            from olive.backend.info import is_checkout
101
 
            if is_checkout(self.comm.get_path()):
102
 
                # we have a checkout, so the local commit checkbox must appear
103
 
                self.checkbutton_local.show()
104
 
            
105
 
            self.textview.modify_font(pango.FontDescription("Monospace"))
106
 
            self.window.show()
107
 
            
108
 
    
109
 
    # This code is from Jelmer Vernooij's bzr-gtk branch
 
161
        # Create the pending merges
 
162
        self._create_pending_merges()
 
163
        
 
164
        # Expand the corresponding expander
 
165
        if self._is_pending:
 
166
            self._expander_merges.set_expanded(True)
 
167
        else:
 
168
            self._expander_files.set_expanded(True)
 
169
        
 
170
        # Display dialog
 
171
        self.vbox.show_all()
 
172
        
 
173
        # Default to Commit button
 
174
        self._button_commit.grab_default()
 
175
    
 
176
    def _on_treeview_files_row_activated(self, treeview, path, view_column):
 
177
        # FIXME: the diff window freezes for some reason
 
178
        treeselection = treeview.get_selection()
 
179
        (model, iter) = treeselection.get_selected()
 
180
        
 
181
        if iter is not None:
 
182
            from diff import DiffWindow
 
183
            
 
184
            _selected = model.get_value(iter, 1)
 
185
            
 
186
            diff = DiffWindow()
 
187
            diff.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
 
188
            diff.set_modal(True)
 
189
            parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
190
            diff.set_diff(self.wt.branch.nick, self.wt, parent_tree)
 
191
            try:
 
192
                diff.set_file(_selected)
 
193
            except errors.NoSuchFile:
 
194
                pass
 
195
            diff.show()
 
196
    
 
197
    @show_bzr_error
 
198
    def _on_commit_clicked(self, button):
 
199
        """ Commit button clicked handler. """
 
200
        textbuffer = self._textview_message.get_buffer()
 
201
        start, end = textbuffer.get_bounds()
 
202
        message = textbuffer.get_text(start, end).decode('utf-8')
 
203
        
 
204
        if not self.pending:
 
205
            specific_files = self._get_specific_files()
 
206
        else:
 
207
            specific_files = None
 
208
 
 
209
        if message == '':
 
210
            response = question_dialog(_('Commit with an empty message?'),
 
211
                                       _('You can describe your commit intent in the message.'))
 
212
            if response == gtk.RESPONSE_NO:
 
213
                # Kindly give focus to message area
 
214
                self._textview_message.grab_focus()
 
215
                return
 
216
 
 
217
        if self._is_checkout:
 
218
            local = self._check_local.get_active()
 
219
        else:
 
220
            local = False
 
221
 
 
222
        if list(self.wt.unknowns()) != []:
 
223
            response = question_dialog(_("Commit with unknowns?"),
 
224
               _("Unknown files exist in the working tree. Commit anyway?"))
 
225
            if response == gtk.RESPONSE_NO:
 
226
                return
 
227
        
 
228
        try:
 
229
            self.wt.commit(message,
 
230
                       allow_pointless=False,
 
231
                       strict=False,
 
232
                       local=local,
 
233
                       specific_files=specific_files)
 
234
        except errors.PointlessCommit:
 
235
            response = question_dialog(_('Commit with no changes?'),
 
236
                                       _('There are no changes in the working tree.'))
 
237
            if response == gtk.RESPONSE_YES:
 
238
                self.wt.commit(message,
 
239
                               allow_pointless=True,
 
240
                               strict=False,
 
241
                               local=local,
 
242
                               specific_files=specific_files)
 
243
        self.response(gtk.RESPONSE_OK)
 
244
 
 
245
    def _pending_merges(self, wt):
 
246
        """ Return a list of pending merges or None if there are none of them. """
 
247
        parents = wt.get_parent_ids()
 
248
        if len(parents) < 2:
 
249
            return None
 
250
        
 
251
        import re
 
252
        from bzrlib.osutils import format_date
 
253
        
 
254
        pending = parents[1:]
 
255
        branch = wt.branch
 
256
        last_revision = parents[0]
 
257
        
 
258
        if last_revision is not None:
 
259
            try:
 
260
                ignore = set(branch.repository.get_ancestry(last_revision))
 
261
            except errors.NoSuchRevision:
 
262
                # the last revision is a ghost : assume everything is new 
 
263
                # except for it
 
264
                ignore = set([None, last_revision])
 
265
        else:
 
266
            ignore = set([None])
 
267
        
 
268
        pm = []
 
269
        for merge in pending:
 
270
            ignore.add(merge)
 
271
            try:
 
272
                m_revision = branch.repository.get_revision(merge)
 
273
                
 
274
                rev = {}
 
275
                rev['committer'] = re.sub('<.*@.*>', '', m_revision.committer).strip(' ')
 
276
                rev['summary'] = m_revision.get_summary()
 
277
                rev['date'] = format_date(m_revision.timestamp,
 
278
                                          m_revision.timezone or 0, 
 
279
                                          'original', date_fmt="%Y-%m-%d",
 
280
                                          show_offset=False)
 
281
                
 
282
                pm.append(rev)
 
283
                
 
284
                inner_merges = branch.repository.get_ancestry(merge)
 
285
                assert inner_merges[0] is None
 
286
                inner_merges.pop(0)
 
287
                inner_merges.reverse()
 
288
                for mmerge in inner_merges:
 
289
                    if mmerge in ignore:
 
290
                        continue
 
291
                    mm_revision = branch.repository.get_revision(mmerge)
 
292
                    
 
293
                    rev = {}
 
294
                    rev['committer'] = re.sub('<.*@.*>', '', mm_revision.committer).strip(' ')
 
295
                    rev['summary'] = mm_revision.get_summary()
 
296
                    rev['date'] = format_date(mm_revision.timestamp,
 
297
                                              mm_revision.timezone or 0, 
 
298
                                              'original', date_fmt="%Y-%m-%d",
 
299
                                              show_offset=False)
 
300
                
 
301
                    pm.append(rev)
 
302
                    
 
303
                    ignore.add(mmerge)
 
304
            except errors.NoSuchRevision:
 
305
                print "DEBUG: NoSuchRevision:", merge
 
306
        
 
307
        return pm
 
308
 
110
309
    def _create_file_view(self):
111
 
        self.file_store = gtk.ListStore(gobject.TYPE_BOOLEAN,
112
 
                                        gobject.TYPE_STRING,
113
 
                                        gobject.TYPE_STRING)
114
 
        self.file_view.set_model(self.file_store)
 
310
        self._file_store = gtk.ListStore(gobject.TYPE_BOOLEAN,   # [0] checkbox
 
311
                                         gobject.TYPE_STRING,    # [1] path to display
 
312
                                         gobject.TYPE_STRING,    # [2] changes type
 
313
                                         gobject.TYPE_STRING)    # [3] real path
 
314
        self._treeview_files.set_model(self._file_store)
115
315
        crt = gtk.CellRendererToggle()
116
316
        crt.set_property("activatable", True)
117
 
        crt.connect("toggled", self._toggle_commit, self.file_store)
118
 
        self.file_view.append_column(gtk.TreeViewColumn(_('Commit'),
 
317
        crt.connect("toggled", self._toggle_commit, self._file_store)
 
318
        self._treeview_files.append_column(gtk.TreeViewColumn(_('Commit'),
119
319
                                     crt, active=0))
120
 
        self.file_view.append_column(gtk.TreeViewColumn(_('Path'),
 
320
        self._treeview_files.append_column(gtk.TreeViewColumn(_('Path'),
121
321
                                     gtk.CellRendererText(), text=1))
122
 
        self.file_view.append_column(gtk.TreeViewColumn(_('Type'),
 
322
        self._treeview_files.append_column(gtk.TreeViewColumn(_('Type'),
123
323
                                     gtk.CellRendererText(), text=2))
124
324
 
125
325
        for path, id, kind in self.delta.added:
126
 
            self.file_store.append([ True, path, _('added') ])
 
326
            marker = osutils.kind_marker(kind)
 
327
            if self.selected is not None:
 
328
                if path == os.path.join(self.wtpath, self.selected):
 
329
                    self._file_store.append([ True, path+marker, _('added'), path ])
 
330
                else:
 
331
                    self._file_store.append([ False, path+marker, _('added'), path ])
 
332
            else:
 
333
                self._file_store.append([ True, path+marker, _('added'), path ])
127
334
 
128
335
        for path, id, kind in self.delta.removed:
129
 
            self.file_store.append([ True, path, _('removed') ])
 
336
            marker = osutils.kind_marker(kind)
 
337
            if self.selected is not None:
 
338
                if path == os.path.join(self.wtpath, self.selected):
 
339
                    self._file_store.append([ True, path+marker, _('removed'), path ])
 
340
                else:
 
341
                    self._file_store.append([ False, path+marker, _('removed'), path ])
 
342
            else:
 
343
                self._file_store.append([ True, path+marker, _('removed'), path ])
130
344
 
131
345
        for oldpath, newpath, id, kind, text_modified, meta_modified in self.delta.renamed:
132
 
            self.file_store.append([ True, oldpath, _('renamed') ])
 
346
            marker = osutils.kind_marker(kind)
 
347
            if text_modified or meta_modified:
 
348
                changes = _('renamed and modified')
 
349
            else:
 
350
                changes = _('renamed')
 
351
            if self.selected is not None:
 
352
                if newpath == os.path.join(self.wtpath, self.selected):
 
353
                    self._file_store.append([ True,
 
354
                                              oldpath+marker + '  =>  ' + newpath+marker,
 
355
                                              changes,
 
356
                                              newpath
 
357
                                            ])
 
358
                else:
 
359
                    self._file_store.append([ False,
 
360
                                              oldpath+marker + '  =>  ' + newpath+marker,
 
361
                                              changes,
 
362
                                              newpath
 
363
                                            ])
 
364
            else:
 
365
                self._file_store.append([ True,
 
366
                                          oldpath+marker + '  =>  ' + newpath+marker,
 
367
                                          changes,
 
368
                                          newpath
 
369
                                        ])
133
370
 
134
371
        for path, id, kind, text_modified, meta_modified in self.delta.modified:
135
 
            self.file_store.append([ True, path, _('modified') ])
 
372
            marker = osutils.kind_marker(kind)
 
373
            if self.selected is not None:
 
374
                if path == os.path.join(self.wtpath, self.selected):
 
375
                    self._file_store.append([ True, path+marker, _('modified'), path ])
 
376
                else:
 
377
                    self._file_store.append([ False, path+marker, _('modified'), path ])
 
378
            else:
 
379
                self._file_store.append([ True, path+marker, _('modified'), path ])
 
380
    
 
381
    def _create_pending_merges(self):
 
382
        if not self.pending:
 
383
            return
 
384
        
 
385
        liststore = gtk.ListStore(gobject.TYPE_STRING,
 
386
                                  gobject.TYPE_STRING,
 
387
                                  gobject.TYPE_STRING)
 
388
        self._treeview_merges.set_model(liststore)
 
389
        
 
390
        self._treeview_merges.append_column(gtk.TreeViewColumn(_('Date'),
 
391
                                            gtk.CellRendererText(), text=0))
 
392
        self._treeview_merges.append_column(gtk.TreeViewColumn(_('Committer'),
 
393
                                            gtk.CellRendererText(), text=1))
 
394
        self._treeview_merges.append_column(gtk.TreeViewColumn(_('Summary'),
 
395
                                            gtk.CellRendererText(), text=2))
 
396
        
 
397
        for item in self.pending:
 
398
            liststore.append([ item['date'],
 
399
                               item['committer'],
 
400
                               item['summary'] ])
136
401
    
137
402
    def _get_specific_files(self):
138
403
        ret = []
139
 
        it = self.file_store.get_iter_first()
 
404
        it = self._file_store.get_iter_first()
140
405
        while it:
141
 
            if self.file_store.get_value(it, 0):
142
 
                ret.append(self.file_store.get_value(it, 1))
143
 
            it = self.file_store.iter_next(it)
 
406
            if self._file_store.get_value(it, 0):
 
407
                # get real path from hidden column 3
 
408
                ret.append(self._file_store.get_value(it, 3))
 
409
            it = self._file_store.iter_next(it)
144
410
 
145
411
        return ret
146
 
    # end of bzr-gtk code
147
412
    
148
413
    def _toggle_commit(self, cell, path, model):
149
414
        model[path][0] = not model[path][0]
150
415
        return
151
 
    
152
 
    def commit(self, widget):
153
 
        textbuffer = self.textview.get_buffer()
154
 
        start, end = textbuffer.get_bounds()
155
 
        message = textbuffer.get_text(start, end)
156
 
        
157
 
        checkbutton_strict = self.glade.get_widget('checkbutton_commit_strict')
158
 
        checkbutton_force = self.glade.get_widget('checkbutton_commit_force')
159
 
        
160
 
        specific_files = self._get_specific_files()
161
 
        
162
 
        self.comm.set_busy(self.window)
163
 
        # merged from Jelmer Vernooij's olive integration branch
164
 
        try:
165
 
            self.wt.commit(message, 
166
 
                           allow_pointless=checkbutton_force.get_active(),
167
 
                           strict=checkbutton_strict.get_active(),
168
 
                           local=self.checkbutton_local.get_active(),
169
 
                           specific_files=specific_files)
170
 
        except errors.NotBranchError:
171
 
            self.dialog.error_dialog(_('Directory is not a branch'),
172
 
                                     _('You can perform this action only in a branch.'))
173
 
            self.comm.set_busy(self.window, False)
174
 
            return
175
 
        except errors.LocalRequiresBoundBranch:
176
 
            self.dialog.error_dialog(_('Directory is not a checkout'),
177
 
                                     _('You can perform local commit only on checkouts.'))
178
 
            self.comm.set_busy(self.window, False)
179
 
            return
180
 
        except errors.PointlessCommit:
181
 
            self.dialog.error_dialog(_('No changes to commit'),
182
 
                                     _('Try force commit if you want to commit anyway.'))
183
 
            self.comm.set_busy(self.window, False)
184
 
            return
185
 
        except errors.ConflictsInTree:
186
 
            self.dialog.error_dialog(_('Conflicts in tree'),
187
 
                                     _('You need to resolve the conflicts before committing.'))
188
 
            self.comm.set_busy(self.window, False)
189
 
            return
190
 
        except errors.StrictCommitFailed:
191
 
            self.dialog.error_dialog(_('Strict commit failed'),
192
 
                                     _('There are unknown files in the working tree.\nPlease add or delete them.'))
193
 
            self.comm.set_busy(self.window, False)
194
 
            return
195
 
        except errors.BoundBranchOutOfDate, errmsg:
196
 
            self.dialog.error_dialog(_('Bound branch is out of date'),
197
 
                                     _('%s') % errmsg)
198
 
            self.comm.set_busy(self.window, False)
199
 
            return
200
 
        except:
201
 
            raise
202
 
        
203
 
        self.close()
204
 
        self.comm.refresh_right()
205
 
        
206
 
    def close(self, widget=None):
207
 
        self.window.destroy()