1#!/usr/local/bin/python3.8
2"""
3    Provide a window showing a list of an app's actions and allow the
4    user to select one
5
6    In addition to the actions defined in the app's .desktop file, a
7    Pin/Unpin action will also be added as appropriate
8
9    The window will function in a similar way to a tooltip i.e.
10    it will appear when the mouse hovers over a dock icon and
11    will disappear if the mouse moves away from the window or
12    the dock applet.
13"""
14
15#
16# Copyright (C) 1997-2003 Free Software Foundation, Inc.
17#
18# This program is free software; you can redistribute it and/or
19# modify it under the terms of the GNU General Public License as
20# published by the Free Software Foundation; either version 2 of the
21# License, or (at your option) any later version.
22#
23# This program is distributed in the hope that it will be useful, but
24# WITHOUT ANY WARRANTY; without even the implied warranty of
25# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
26# General Public License for more details.
27#
28# You should have received a copy of the GNU General Public License
29# along with this program; if not, write to the Free Software
30# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
31# 02110-1301, USA.
32#
33# Author:
34#     Robin Thompson
35
36# do not change the value of this variable - it will be set during build
37# according to the value of the --with-gtk3 option used with .configure
38build_gtk2 = False
39
40import gi
41
42if build_gtk2:
43    gi.require_version("Gtk", "2.0")
44    gi.require_version("Wnck", "1.0")
45else:
46    gi.require_version("Gtk", "3.0")
47    gi.require_version("Wnck", "3.0")
48
49gi.require_version("MatePanelApplet", "4.0")
50
51from gi.repository import Gtk
52from gi.repository import Wnck
53from gi.repository import GdkPixbuf
54from gi.repository import Gio
55from gi.repository import Gdk
56from gi.repository import GObject
57from gi.repository import MatePanelApplet
58from gi.repository import Pango
59
60from dock_popup import DockPopup
61
62from log_it import log_it as log_it
63
64CONST_MAX_TITLE_WIDTH = 300         # Max width of the action text
65CONST_SEP = "--------------------"  # text which denotes tree view item is a separator
66
67
68class DockActionList(DockPopup):
69
70    """ Descendent of Dockup to provide a list of a running app's
71        open windows
72
73    """
74
75    def __init__(self, wnck_screen, panel_orient, scroll_adj):
76        """
77        create the window and its contents
78
79        Args:
80            wnck_screen: the wnck_screen of the applet
81            panel_orient : the orientation of the panel
82            scroll_adj   : an adjustment to be applied to the window position
83                           because the dock has scrolling enabled
84        """
85
86        # call the base classes constructor
87        DockPopup.__init__(self, wnck_screen, panel_orient, scroll_adj)
88
89        # we use a treeview to list each action, so initialise it and its
90        # liststore
91        self.__tree_view = Gtk.TreeView()
92
93        if not build_gtk2:
94            self.__tree_view.set_valign(Gtk.Align.START)
95            self.__tree_view.set_halign(Gtk.Align.START)
96            self.__tree_view.hexpand = True
97            self.__tree_view.vexpand = True
98
99        self.__tree_view.set_headers_visible(False)
100
101        # turn grid lines off, although they still seem to appear in some
102        # themes e.g. Menta
103        self.__tree_view.set_grid_lines(Gtk.TreeViewGridLines.NONE)
104
105        self.__tree_view.set_hover_selection(True)
106
107        # the liststore needs to contain an icon, the action text, and the
108        # action itself
109        self.__list_store = Gtk.ListStore(str, Gtk.Action, GdkPixbuf.Pixbuf)
110
111        self.__icon_renderer = Gtk.CellRendererPixbuf()
112        self.__title_renderer = Gtk.CellRendererText()
113
114        # set default cell colours and padding
115        self.__title_renderer.set_padding(2, 6)
116        self.set_bg_col(32, 32, 32)
117
118        # create columns for the treeview
119        self.__col_icon = Gtk.TreeViewColumn("",
120                                             self.__icon_renderer,
121                                             pixbuf=2)
122
123        self.__col_title = Gtk.TreeViewColumn("",
124                                              self.__title_renderer,
125                                              text=0)
126
127        self.__col_title.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
128        self.__col_title.set_expand(True)
129
130        self.__col_title.set_max_width(CONST_MAX_TITLE_WIDTH)
131        self.__col_title.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
132
133        # add the columns
134        self.__tree_view.set_model(self.__list_store)
135        self.__tree_view.append_column(self.__col_icon)
136        self.__tree_view.append_column(self.__col_title)
137        self.__tree_view.set_row_separator_func(self.check_sep)
138
139        # add the treeview to the window
140        self.set_main_widget(self.__tree_view)
141
142        self.__tree_view.connect("button-release-event", self.button_release)
143        self.__tree_view.connect("size-allocate", self.treeview_allocate)
144
145    def treeview_allocate(self, widget, allocation):
146        """ Event handler for the tree view size-allocate event
147
148        If the title column has expanded to its maximum width, ellipsize the
149        title text...
150        """
151
152        if self.__col_title.get_width() == CONST_MAX_TITLE_WIDTH:
153            self.__col_title.set_min_width(CONST_MAX_TITLE_WIDTH)
154            self.__title_renderer.set_property("ellipsize",
155                                               Pango.EllipsizeMode.END)
156
157        self.__tree_view.get_selection().unselect_all()
158
159    def set_colours(self, panel_colour):
160        """ Sets the treeview colours (background, foreground and
161            highlight) to match the window colours.
162
163        Note : set_colours must have been called first so that
164        the window colours are set correctly
165        """
166
167        DockPopup.set_colours(self, panel_colour)
168
169        # set strings used to set widget colours - we can't change
170        # the highlight colour, only foreground and background
171        r, g, b = self.bg_col
172        bg_str = "#%.2x%.2x%.2x" % (r, g, b)
173        r, g, b = self.fg_col
174        fg_str = "#%.2x%.2x%.2x" % (r, g, b)
175
176        # now set the treeview colours
177        self.__title_renderer.set_property("cell-background", bg_str)
178        self.__title_renderer.set_property("foreground", fg_str)
179
180        self.__icon_renderer.set_property("cell-background", bg_str)
181
182    def get_num_rows(self):
183        """ Returns the number of rows of data in the list store
184        """
185
186        return (self.__list_store.iter_n_children(None))
187
188    def button_release(self, widget, event):
189        """ Handler for the button release event
190
191        If the middle or right mouse button was pressed, do nothing
192
193        Otherwise, activate the selected item's action
194
195        Hide the action list
196
197        Args:
198
199            widget : the widget the received the signal i.e. our treeview
200            event  : the event parameters
201
202        Returns:
203            True: to stop any other handlers from being invoked
204        """
205
206        if event.button != 1:
207            return False  # let other handlers run
208
209        path, col, xrel, yrel = self.__tree_view.get_path_at_pos(event.x, event.y)
210        sel_iter = self.__list_store.get_iter(path)
211
212        action = self.__list_store.get_value(sel_iter, 1)
213        title = self.__list_store.get_value(sel_iter, 0)
214        if action is not None:
215            self.hide()
216            action.activate()
217
218        return True
219
220    def add_separator(self):
221        """ Convenience method to add a separator to the list
222
223        If there are no items currently in the list then the separator
224        won't be added
225        """
226
227        if len(self.__list_store) > 0:
228            self.add_to_list(CONST_SEP, None, False)
229
230    def add_to_list(self, title, action, show_icon):
231        """ Add an item to the action list
232
233        Args:
234            title - the title of the window or the action
235            action - a GTK Action to be activated if the item is clicked
236            show_icon - if True the app's icon will be shown alongside the
237                        item in the list
238        """
239
240        if show_icon:
241            app_icon = self.app_pb
242        else:
243            app_icon = None
244
245        self.__list_store.append([title, action, app_icon])
246
247    def clear_act_list(self):
248        """ Clear the list of open windows """
249
250        self.__list_store.clear()
251
252    def win_button_press(self, widget, event):
253        """ this is for debug puposes only"""
254        Gtk.main_quit()
255
256    def check_sep(self, model, iter, data=None):
257        """ Check to see if the current row is to be displayed as a separator
258
259            Args :
260                model : the treeview model (will be self.__list_store)
261                iter : the row in the model we're interested in
262                data : user defined data
263
264            Returns:
265                Bool
266        """
267
268        title = model.get_value(iter, 0)
269        return title == CONST_SEP
270
271
272def main():
273    """
274    main function - debugging code goes here
275    """
276
277#    thewin = DockWinList()
278#    thewin.set_app_name("Testing....")
279#    thewin.add_to_list(None, False, "Win 1")
280#    thewin.add_to_list(None, True, "Win 2 is active")
281#    thewin.add_to_list(None, False, "Win 3")
282#    thewin.show_all()
283
284#    thewin.move(100, 110)
285#    pos = thewin.get_position()
286#    size = thewin.get_size()
287#    print("pos %d %d" %(pos[0], pos[1]))
288#    print("size %d %d" %(size[0], size[1]))
289#    thewin.add_mouse_area(Gdk.Rectangle(pos[0]-15, pos[1]-15, size[0]+30, size[1]+30))
290#    thewin.add_mouse_area(Gdk.Rectangle(0, 0, 48, 500))
291#    thewin.add_mouse_area(Gdk.Rectangle(48, 110, 100, size[1]))
292#    Gtk.main()
293    return
294
295
296if __name__ == "__main__":
297    main()
298